Mockito:如何在方法中创建的对象上调用方法?

mre*_*mre 291 java junit unit-testing mockito

我是Mockito的新手.

鉴于下面的类,我如何使用Mockito来验证someMethod在调用之后是否被调用了一次foo

public class Foo
{
    public void foo(){
        Bar bar = new Bar();
        bar.someMethod();
    }
}
Run Code Online (Sandbox Code Playgroud)

我想进行以下验证通话,

verify(bar, times(1)).someMethod();
Run Code Online (Sandbox Code Playgroud)

在哪里bar是一个模拟的实例Bar.

cst*_*rtz 327

依赖注入

如果您注入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();
  }
}
Run Code Online (Sandbox Code Playgroud)

在你的测试方法中,你可以像这样注入一个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();
}
Run Code Online (Sandbox Code Playgroud)

额外奖励:这是TDD如何推动代码设计的一个例子.

  • 不是我知道的.但是,我并不是建议您仅为单元测试修改类.这实际上是关于干净代码和SRP的对话.或者......在类Foo中使用方法foo()来构造Bar对象.如果答案是肯定的,那么这是一个实现细节,你不应该担心具体测试交互(参考@Michael的答案).如果答案是否定的,那么你正在修改课程,因为你的测试难度是一个红旗,你的设计需要一点改进(因此我添加的奖励是TDD驱动设计的方式). (7认同)
  • 有没有办法在不修改单元测试类的情况下执行此操作? (5认同)
  • `bar bar = mock(Bar.class)`而不是`Bar bar = new Bar();` (5认同)
  • 你也可以模仿工厂:`BarFactory myFactory = mock(BarFactory.class); 当(myFactory.createBar())thenReturn(巴);` (3认同)
  • @你真棒测试驱动开发 (3认同)
  • 你能把一个"真实"的对象传递给Mockito的"验证"吗? (2认同)

Mic*_*vis 18

经典的回答是,"你没有." 您测试公共API Foo,而不是其内部.

是否存在受到影响的Foo对象(或不太好,环境中的其他对象)的任何行为foo()?如果是这样,请测试一下.如果没有,该方法有何作用?

  • 只测试公共API是好的,直到存在需要测试的副作用的真正错误.例如,检查私有方法是否正确关闭其HTTP连接是不合适的,直到您发现私有方法*没有*正确关闭其连接,因此导致大量问题.那时,Mockito和`verify()`确实变得非常有帮助,即使你不再在集成测试的神圣祭坛上崇拜. (11认同)
  • 那你在这里测试什么呢?`Foo`的公共API是`public void foo()`,其内部与_only_ bar相关. (4认同)
  • 我同意DuffJ的观点,尽管功能编程很有趣,但有时代码与外界交互。不管您将其称为“内部”,“副作用”还是“功能”,您都绝对希望测试该交互:是否发生了交互,是否发生了正确的次数以及正确的参数。@Stijn:这可能是一个不好的例子(但是,如果应打开多个连接,而仅其中一些关闭,则很有趣)。更好的示例是检查天气是否已通过连接发送了正确的数据。 (3认同)
  • @Dawngerpony私有方法?这些应该避免。关闭http连接的方法应该是public的。然后,您将对该方法进行单独的单元测试,该方法会模拟连接并验证是否调用了“close”。简单的。 (2认同)

ras*_*orp 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
    }
}
Run Code Online (Sandbox Code Playgroud)

而你的测试班:

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

那么调用你的foo方法的类就会这样做:

public class thirdClass {

   public void someOtherMethod() {
      Foo myFoo = new Foo();
      myFoo.foo(null);
   }
}
Run Code Online (Sandbox Code Playgroud)

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

当然缺点是你允许调用者设置Bar Object.

希望能帮助到你.

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

jav*_*e42 8

使用的示例代码解决方案 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();

    }

}
Run Code Online (Sandbox Code Playgroud)

JUnit输出 JUnit输出


siu*_*lki 8

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

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

  1. 构造函数注入
  2. 物业二传手注射
  3. 现场注射

更多信息在docs中

以下是现场注入的示例:

类别:

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

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

public class Bar
{
    public void someMethod()
    {
         //something
    }
}
Run Code Online (Sandbox Code Playgroud)

测试:

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


Joh*_*n B 7

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

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