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

bwo*_*nes 57 java junit unit-testing mockito

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

public class TestedClass {
  public LoginContext login(String user, String password) {
    LoginContext lc = new LoginContext("login", callbackHandler);
  }
}
Run Code Online (Sandbox Code Playgroud)

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

Tom*_*icz 54

对于未来,我会推荐Eran Harel的答案(重构转移new到可以嘲笑的工厂).但是,如果您不想更改原始源代码,请使用非常方便且独特的功能:间谍.从文档:

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

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

在你的情况下你应该写:

TestedClass tc = spy(new TestedClass());
LoginContext lcMock = mock(LoginContext.class);
when(tc.login(anyString(), anyString())).thenReturn(lcMock);
Run Code Online (Sandbox Code Playgroud)

  • 是的,使用可以被嘲笑的工厂才是真正的答案。使用间谍是有争议的,在我看来应该避免。 (3认同)
  • _重构将新内容转移到可以被嘲笑的工厂_我不明白这一点。您刚刚将同样的问题转移到了新的地方。 (2认同)
  • @ChristopherSchneider是的,但是在新的地方(通常是您的测试类),您可以对工厂进行编码以返回您在测试中实例化的对象,这样您就可以在测试验证中使用该对象。 (2认同)

Mur*_*nik 33

我全都是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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

为了完整起见,值得一提的是第三种技术 - 在调用运算符时使用PowerMock注入模拟对象new.但是,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
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 用于说明场景的+1通常是这种情况,因为大多数需要这种方法并且重构不可能/困难的方法是旧/遗留代码! (5认同)
  • 这种方法的一个大问题是你必须在`@ PrepareForTest`中加入`TestedClass`,它会在声纳和eclemma中自动使整个类覆盖为0.这可能是由于其中一个覆盖率分析工具中的错误造成的 (4认同)
  • `如果没有其他选择,唯一的选择必须是好的选择.+ 1为Sherlock Holmes先生.;) (2认同)
  • Powermock [不适用于 JUnit5](https://github.com/powermock/powermock/issues/929) 目前 (2认同)

Era*_*rel 23

您可以使用工厂来创建登录上下文.然后你可以模拟工厂并返回你想要的任何测试.

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();
}
Run Code Online (Sandbox Code Playgroud)

  • 这将要求我所拥有的每个班级都需要一个工厂.对于POJO,我发现创建这样的工厂是一种过度杀伤力.是否有其他测试框架的替代方案? (5认同)

小智 5

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

-测试类别:

    @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
        }
    }
Run Code Online (Sandbox Code Playgroud)

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