使用Mockito在其中调用new()调用的测试类

时间:2011-05-07 09:12:40

标签: java unit-testing junit mockito

我有一个遗留类,它包含一个new()调用来实例化LoginContext():

public class TestedClass {
  public LoginContext login(String user, String password) {
    LoginContext lc = new LoginContext("login", callbackHandler);
  }
}

我想使用Mockito来测试这个类来模拟LoginContext,因为它要求在实例化之前设置JAAS安全性东西,但是我不知道如何在不更改login()方法来外化LoginContext的情况下这样做。是否可以使用Mockito来模拟LoginContext类?

7 个答案:

答案 0 :(得分:47)

对于未来,我建议Eran Harel's answer(重构移动new到可以模拟的工厂)。但是,如果您不想更改原始源代码,请使用非常方便且独特的功能:间谍。来自documentation

  

您可以创建真实对象的间谍。当你使用间谍时,会调用真正的方法(除非方法被存根)。

     

真正的间谍应该谨慎使用,例如在处理遗留代码时。

在你的情况下你应该写:

TestedClass tc = spy(new TestedClass());
LoginContext lcMock = mock(LoginContext.class);
when(tc.login(anyString(), anyString())).thenReturn(lcMock);

答案 1 :(得分:28)

我全都是Eran Harel的解决方案,在不可能的情况下,Tomasz Nurkiewicz关于间谍活动的建议非常好。但是,值得注意的是,在某些情况下两者都不适用。例如。如果login方法有点"更强大":

public class TestedClass {
    public LoginContext login(String user, String password) {
        LoginContext lc = new LoginContext("login", callbackHandler);
        lc.doThis();
        lc.doThat();
    }
}

...这是旧代码,无法重构以将新LoginContext的初始化提取到它自己的方法并应用上述解决方案之一。

为了完整性'为了清楚,值得一提的是第三种技术 - 在调用new运算符时使用PowerMock注入模拟对象。但PowerMock并不是一颗银弹。它的工作原理是对它所模拟的类应用字节码操作,如果被测试的类使用字节代码操作或反射,并且至少根据我的个人经验,已经知道会给测试带来性能损失,这可能是狡猾的做法。然后,如果没有其他选项,唯一的选择必须是好的选择:

@RunWith(PowerMockRunner.class)
@PrepareForTest(TestedClass.class)
public class TestedClassTest {

    @Test
    public void testLogin() {
        LoginContext lcMock = mock(LoginContext.class);
        whenNew(LoginContext.class).withArguments(anyString(), anyString()).thenReturn(lcMock);
        TestedClass tc = new TestedClass();
        tc.login ("something", "something else");
        // test the login's logic
    }
}

答案 2 :(得分:21)

您可以使用工厂创建登录上下文。然后你可以嘲笑工厂并返回你想要的任何测试。

public class TestedClass {
  private final LoginContextFactory loginContextFactory;

  public TestedClass(final LoginContextFactory loginContextFactory) {
    this.loginContextFactory = loginContextFactory;
  }

  public LoginContext login(String user, String password) {
    LoginContext lc = loginContextFactory.createLoginContext();
  }
}

public interface LoginContextFactory {
  public LoginContext createLoginContext();
}

答案 3 :(得分:4)

    public class TestedClass {
    public LoginContext login(String user, String password) {
        LoginContext lc = new LoginContext("login", callbackHandler);
        lc.doThis();
        lc.doThat();
    }
  }

- 测试类:

    @RunWith(PowerMockRunner.class)
    @PrepareForTest(TestedClass.class)
    public class TestedClassTest {

        @Test
        public void testLogin() {
            LoginContext lcMock = mock(LoginContext.class);
            whenNew(LoginContext.class).withArguments(anyString(), anyString()).thenReturn(lcMock);
//comment: this is giving mock object ( lcMock )
            TestedClass tc = new TestedClass();
            tc.login ("something", "something else"); ///  testing this method.
            // test the login's logic
        }
    }

从testLogin(){调用实际方法tc.login ("something", "something else");时{ - 此LoginContext lc设置为null并在调用lc.doThis();

时抛出NPE

答案 4 :(得分:2)

不是我知道的,但是当您创建要测试的TestedClass实例时,如何做这样的事情:

TestedClass toTest = new TestedClass() {
    public LoginContext login(String user, String password) {
        //return mocked LoginContext
    }
};

另一种选择是使用Mockito创建一个TestedClass实例,让模拟实例返回一个LoginContext。

答案 5 :(得分:1)

在可以修改被测试类以及何时需要避免字节代码操作,保持速度快或最小化第三方依赖性的情况下,我采用工厂来提取{{1操作。

new

有了这个,你可以轻松地对Pojo对象进行测试,断言和验证调用:

public class TestedClass {

    interface PojoFactory { Pojo getNewPojo(); }

    private final PojoFactory factory;

    /** For use in production - nothing needs to change. */
    public TestedClass() {
        this.factory = new PojoFactory() {
            @Override
            public Pojo getNewPojo() {
                return new Pojo();
            }
        };
    }

    /** For use in testing - provide a pojo factory. */
    public TestedClass(PojoFactory factory) {
        this.factory = factory;
    }

    public void doSomething() {
        Pojo pojo = this.factory.getNewPojo();
        anythingCouldHappen(pojo);
    }
}

如果public void testSomething() { Pojo testPojo = new Pojo(); TestedClass target = new TestedClass(new TestedClass.PojoFactory() { @Override public Pojo getNewPojo() { return testPojo; } }); target.doSomething(); assertThat(testPojo.isLifeStillBeautiful(), is(true)); } 有多个构造函数,您必须使用额外参数进行复制,则可能会出现此方法的唯一缺点。

出于SOLID的原因,您可能希望将PojoFactory接口改为Pojo类,以及生产工厂。

TestClass

答案 6 :(得分:1)

我碰巧处于一种特殊的情况下,我的用例类似于Mureinik中的一个,但是我最终使用了Tomasz Nurkiewicz的解决方案。

方法如下:

class TestedClass extends AARRGGHH {
    public LoginContext login(String user, String password) {
        LoginContext lc = new LoginContext("login", callbackHandler);
        lc.doThis();
        lc.doThat();
        return lc;
    }
}

现在,PowerMockRunner无法初始化TestedClass,因为它扩展了AARRGGHH,这反过来又进行了更多的上下文初始化...您将看到此路径将我引向何方:我将需要在几层上进行模拟。显然有巨大的气味。

我发现一个TestedClass重构最少的nice hack:我创建了一个小方法

LoginContext initLoginContext(String login, CallbackHandler callbackHandler) {
    new lc = new LoginContext(login, callbackHandler);
}

此方法的范围必须为package

然后您的测试存根将如下所示:

LoginContext lcMock = mock(LoginContext.class)
TestedClass testClass = spy(new TestedClass(withAllNeededArgs))
doReturn(lcMock)
    .when(testClass)
    .initLoginContext("login", callbackHandler)

技巧就完成了...