Mockito:如何验证在方法内创建的对象上是否调用过方法?


322

我是Mockito的新手。

给定下面的类,如何someMethod在调用后使用Mockito验证一次foo被调用的对象呢?

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

我想打以下电话,

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

bar的模拟实例在哪里Bar


2
stackoverflow.com/questions/6520242/…-但我不想使用PowerMock。
mre

更改API或PowerMock。两者之一。
约翰B

如何覆盖这样的东西?公共同步void start(BundleContext bundleContext)引发异常{BundleContext bc = bundleContext; logger.info(“ STARTING HTTP SERVICE BUNDLE”); this.tracker = new ServiceTracker(bc,HttpService.class.getName(),null){@Override public Object addingService(ServiceReference serviceRef){httpService =(HttpService)super.addingService(serviceRef); registerServlets(); 返回httpService; }}}
ShAkKiR

Answers:


365

依赖注入

如果注入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如何驱动代码设计的一个示例。


6
有没有一种方法无需修改类以进行单元测试?
mre

6
Bar bar = mock(Bar.class)而不是Bar bar = new Bar();
John B

7
不是我知道的。但是,我不建议您修改类仅用于单元测试。这实际上是关于干净代码和SRP的对话。或者..构造Bar对象是类Foo中的foo()方法的责任。如果答案是肯定的,那么这是一个实现细节,您不必担心要专门测试交互(请参阅@Michael的答案)。如果答案是否定的,那么您正在修改课程,因为您的测试困难是一个危险信号,即您的设计需要一点改进(因此,我在TDD驱动设计时所获得的好处)。
csturtz 2012年

3
您可以将“真实”对象传递给Mockito的“验证”吗?
约翰B

4
您也可以嘲笑工厂: BarFactory myFactory = mock(BarFactory.class); when(myFactory.createBar()).thenReturn(bar);
levsa 2013年

18

最经典的回答是:“您不这样做”。您测试的公共API Foo,而不是其内部。

是否存在受该Foo对象影响的任何行为(或者,环境中的其他不良对象)foo()?如果是这样,请进行测试。如果不是,该方法有什么作用?


4
那么,您将在这里实际测试什么?的公共API Foopublic void foo(),其中内部与酒吧相关。
behelit '16

15
仅测试公共API是可以的,直到有真正的带有副作用的bug需要测试。例如,检查私有方法是否正确关闭其HTTP连接是过大的尝试,直到您发现私有方法正确关闭其连接,从而造成了严重的问题。到那时verify(),即使您不再在集成测试的圣坛上崇拜,Mockito 的确也变得非常有帮助。
Dawngerpony

@DuffJ我不使用Java,但这听起来像是编译器或代码分析工具应该检测到的东西。
user247702

3
我同意DuffJ的观点,尽管函数编程很有趣,但有一点您的代码与外界交互。称它为“内部”,“副作用”或“功能”无关紧要,您肯定要测试这种交互:如果发生,是否发生正确的次数以及正确的参数。@Stijn:这可能是一个不好的例子(但是,如果应打开多个连接,而仅其中一些关闭,则很有趣)。更好的示例是检查天气是否已通过连接发送了正确的数据。
安德拉斯·巴拉兹(AndrasBalázsLajtha),

13

如果您不想使用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对象。

希望能帮助到你。


3
我认为这是一种反模式。期间应注入依赖项。仅允许出于测试目的而注入可选的依赖关系是有意避免改进代码,并且有意测试与生产环境中运行的代码不同的内容。这两件事都是可怕的,可怕的事情。
ErikE

8

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

  • 模拟所有1.10.8
  • powermock核心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输出


8

我认为Mockito @InjectMocks是要走的路。

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

  1. 构造器注入
  2. 属性设定器注入
  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();
    }
}

3

是的,如果您确实想要/需要这样做,可以使用PowerMock。这应被视为万不得已。使用PowerMock可以使它从调用返回构造函数的模拟。然后在模拟上进行验证。也就是说,csturtz的答案是“正确的”。

这是新对象的模拟构建的链接


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.