java 使用Mockito测试包含new()调用的类

iecba09b  于 2023-01-04  发布在  Java
关注(0)|答案(7)|浏览(256)

我有一个遗留类,其中包含一个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类?

ca1c2owp

ca1c2owp1#

对于未来,我建议使用Eran Harel's answer(将new重构到可以被模仿的工厂).但是如果你不想改变原始源代码,使用非常方便和独特的功能:间谍。从文档中:
你可以创建真实的对象的间谍。当你使用间谍时,real方法被调用(除非一个方法被存根化)。
真实的的间谍应该谨慎使用,偶尔使用,例如在处理遗留代码时。
在您的情况下,您应填写:

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

hpxqektj2#

我完全支持Eran Harel's solution,在不可能的情况下,Tomasz Nurkiewicz's suggestion for spying是最好的。然而,值得注意的是,有些情况下两者都不适用。例如,如果login方法有点"强大":

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

......而且这是旧代码,无法重构以将新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
    }
}
    • 编辑:**

Mockito的现代版本提供了类似的功能,而不需要额外的PowerMock库,该库具有mockito-inline依赖项(而不是mockito-core依赖项):

public class TestedClassTest {
    @Test
    public void testLogin() {
        try (MockedConstruction<LoginContext> mockedConstruction = 
             Mockito.mockConstruction(LoginContext.class)) {
            TestedClass tc = new TestedClass();
            tc.login("something", "something else");
            // test the login's logic
        }
    }
}
camsedfj

camsedfj3#

您可以使用一个工厂来创建登录上下文,然后您可以模拟这个工厂并返回任何您想要的测试结果。

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();
}
oknwwptz

oknwwptz4#

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设置为空,并在调用lc.doThis();时抛出NPE

y4ekin9u

y4ekin9u5#

据我所知没有,但是当你创建一个你想要测试的TestedClass的示例时,做这样的事情怎么样:

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

另一种选择是使用Mockito创建TestedClass的示例,并让被模拟的示例返回LoginContext。

u1ehiz5o

u1ehiz5o6#

在被测类可以被修改的情况下,当需要避免字节码操作、保持速度或最小化第三方依赖性时,下面是我对使用工厂提取new操作的看法。

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);
    }
}

有了这些,对Pojo对象的测试、Assert和验证调用就很容易了:

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));
}

如果TestClass有多个构造函数,而您必须使用额外的参数来复制这些构造函数,那么这种方法的唯一缺点可能会出现。
出于可靠的原因,您可能希望将PojoFactory接口放在Pojo类上,以及生产工厂上。

public class Pojo {

    interface PojoFactory { Pojo getNewPojo(); }

    public static final PojoFactory productionFactory = 
        new PojoFactory() {
            @Override 
            public Pojo getNewPojo() {
                return new Pojo();
            }
        };
6uxekuva

6uxekuva7#

我碰巧遇到了一个特殊的情况,我的用例类似于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,而AARRGGHH又执行了更多的上下文初始化......您可以看到这条路径将我引向何处:我需要在几层上嘲笑。显然是一种巨大的气味。
我找到了一个nice hack,它对TestedClass的重构非常少:我创建了一个小方法

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)

魔术就完成了。

相关问题