Mockito:如何验证方法被调用在一个方法内创建的对象?

我是Mockito的新手。

对于下面的类,如何使用Mockito来验证someMethodfoo被调用后恰好被调用了一次?

public class Foo
{
public void foo(){
Bar bar = new Bar();
bar.someMethod();
}
}

我想打电话确认一下,

verify(bar, times(1)).someMethod();

其中barBar的模拟实例。

812950 次浏览

最经典的回答是:“你不需要。”你测试的是Foo的公共API,而不是它的内部。

是否有任何Foo对象(或者,不太好的,环境中的其他对象)的行为受到foo()的影响?如果是,测试一下。如果不是,该方法做什么?

依赖注入 .

如果您注入Bar实例,或用于创建Bar实例的工厂(或其他483种方法中的一种),您将拥有执行测试所需的访问权限。

工厂的例子:

给定一个这样的Foo类:

public class Foo {
private BarFactory barFactory;


public Foo(BarFactory factory) {
this.barFactory = factory;
}


public void foo() {
Bar bar = this.barFactory.createBar();
bar.someMethod();
}
}

在你的测试方法中,你可以像这样注入一个BarFactory:

@Test
public void testDoFoo() {
Bar bar = mock(Bar.class);
BarFactory myFactory = new BarFactory() {
public Bar createBar() { return bar;}
};
  

Foo foo = new Foo(myFactory);
foo.foo();


verify(bar, times(1)).someMethod();
}

奖励:这是TDD(测试驱动开发)如何驱动代码设计的一个例子。

是的,如果你真的想要/需要这样做,你可以使用PowerMock。这应该被视为最后的手段。使用PowerMock,可以使它从对构造函数的调用中返回一个模拟。然后在模拟上进行验证。也就是说,csturtz的答案是“正确的”。

下面是新对象的模拟构造的链接

如果您不想使用DI或工厂。你可以用一种小技巧重构你的类:

public class Foo {
private Bar bar;


public void foo(Bar bar){
this.bar = (bar != null) ? bar : new Bar();
bar.someMethod();
this.bar = null;  // for simulating local scope
}
}

和你的测试类:

@RunWith(MockitoJUnitRunner.class)
public class FooTest {
@Mock Bar barMock;
Foo foo;


@Test
public void testFoo() {
foo = new Foo();
foo.foo(barMock);
verify(barMock, times(1)).someMethod();
}
}

然后调用foo方法的类会这样做:

public class thirdClass {


public void someOtherMethod() {
Foo myFoo = new Foo();
myFoo.foo(null);
}
}

正如你所看到的,当以这种方式调用方法时,你不需要在任何其他调用foo方法的类中导入Bar类,这可能是你想要的。

当然,缺点是您允许调用者设置Bar对象。

希望能有所帮助。

使用PowerMockito.whenNew的示例代码的解决方案

  • mockito-all 1.10.8
  • powermock-core 1.6.1
  • powermock-module-junit4 1.6.1
  • powermock-api-mockito 1.6.1
  • junit 4.12

FooTest.java

package foo;


import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;


//Both @PrepareForTest and @RunWith are needed for `whenNew` to work
@RunWith(PowerMockRunner.class)
@PrepareForTest({ Foo.class })
public class FooTest {


// Class Under Test
Foo cut;


@Mock
Bar barMock;


@Before
public void setUp() throws Exception {
cut = new Foo();


}


@After
public void tearDown() {
cut = null;


}


@Test
public void testFoo() throws Exception {


// Setup
PowerMockito.whenNew(Bar.class).withNoArguments()
.thenReturn(this.barMock);


// Test
cut.foo();


// Validations
Mockito.verify(this.barMock, Mockito.times(1)).someMethod();


}


}

<强> JUnit输出 JUnit Output < / p >

我认为Mockito @InjectMocks是正确的方法。

根据你的意图,你可以使用:

  1. 构造函数注入
  2. 属性setter注入
  3. 字段注入

更多信息在文档

下面是一个字段注入的例子:

类:

public class Foo
{
private Bar bar = new Bar();


public void foo()
{
bar.someMethod();
}
}


public class Bar
{
public void someMethod()
{
//something
}
}

测试:

@RunWith(MockitoJUnitRunner.class)
public class FooTest
{
@Mock
Bar bar;


@InjectMocks
Foo foo;


@Test
public void FooTest()
{
doNothing().when( bar ).someMethod();
foo.foo();
verify(bar, times(1)).someMethod();
}
}

另一种简单的方法是向bar.someMethod()中添加一些日志语句,然后确定在测试执行时可以看到所述消息,参见示例:如何对日志中的消息进行JUnit断言

当你的Bar.someMethod()是private时,这是特别方便的。

我今天遇到了这个问题,我不想使用PowerMock或其他东西。我只是想做一个测试,确保某个方法被调用。我找到了这篇文章,发现没有人提到过这种方法。

有一种方法可以在不增加更多依赖或类似的情况下实现这一点,技术含量相当低,但它是有效的:

@Test
public void testSomeMethodIsCalledOnce() throws Exception {
final AtomicInteger counter = new AtomicInteger(0);
Mockito.when(someObject.theMethodIWant(anyString()))
.then((Answer<ReturnValue>) __ -> {
teller.incrementAndGet();
return theExpectedAnswer;
});
theObjectUnderTest.theMethod(someTestValue);


assertEquals(1, teller.get());
}

这很简单,很容易看出发生了什么。当我想要的方法被调用时(这里是模拟的),做这些事情。其中有一个调用incrementAndGet来获取AtomicInteger。你可以在这里使用了一个int[],但在我看来这并不清楚。我们用的是最终值,可以加。这是我们用的的极限。

这有点粗糙,但它以简单而直接的方式完成了工作。至少如果你知道你的羔羊和Mockito。