ChatGPT解决这个技术问题 Extra ChatGPT

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

我是 Mockito 的新手。

给定下面的类,我如何使用 Mockito 来验证 someMethodfoo 被调用后被调用了一次?

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

我想拨打以下验证电话,

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

其中 barBar 的模拟实例。

stackoverflow.com/questions/6520242/… - 但我不想使用 PowerMock。
更改 API 或 PowerMock。两者之一。
如何覆盖这样的事情?公共同步无效开始(BundleContext bundleContext)抛出异常{BundleContext bc = bundleContext; logger.info("开始 HTTP 服务包"); this.tracker = new ServiceTracker(bc, HttpService.class.getName(), null) { @Override public Object addedService(ServiceReference serviceRef) { httpService = (HttpService) super.addingService(serviceRef); registerServlets();返回http服务; }}}

R
Riyafa Abdul Hameed

Dependency Injection

如果您注入 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(测试驱动开发)如何驱动代码设计的示例。


有没有办法在不修改单元测试类的情况下做到这一点?
Bar bar = mock(Bar.class) 而不是 Bar bar = new Bar();
不是我知道的。但是,我并不是建议您仅为了单元测试而修改该类。这实际上是关于干净代码和 SRP 的对话。或者.. Foo 类中的方法 foo() 是否有责任构造一个 Bar 对象。如果答案是肯定的,那么这是一个实现细节,您不必担心专门测试交互(请参阅@Michael 的答案)。如果答案是否定的,那么您正在修改课程,因为您的测试困难是一个危险信号,表明您的设计需要一点改进(因此我添加了关于 TDD 如何驱动设计的奖励)。
您可以将“真实”对象传递给 Mockito 的“验证”吗?
您还可以模拟工厂:BarFactory myFactory = mock(BarFactory.class); when(myFactory.createBar()).thenReturn(bar);
M
Michael Brewer-Davis

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

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


那么你会在这里实际测试什么? Foo 的公共 API 是 public void foo(),其中内部条相关。
只测试公共 API 是可以的,直到有真正的带有副作用的 bug 需要测试。例如,检查私有方法是否正确关闭其 HTTP 连接是多余的,直到您发现私有方法没有正确关闭其连接,从而导致严重问题。到那时,Mockito 和 verify() 确实变得非常有帮助,即使您不再崇拜集成测试的圣坛。
@DuffJ我不使用Java,但这听起来像是您的编译器或代码分析工具应该检测到的东西。
我同意 DuffJ 的观点,虽然函数式编程很有趣,但有时你的代码会与外界交互。不管你称它为“内部”、“副作用”还是“功能”,你肯定想要测试这种交互:如果它发生了,它是否发生了正确的次数和正确的参数。 @Stijn:这可能是一个不好的例子(但是如果应该打开多个连接,并且只有其中一些连接关闭,那么它就会变得有趣)。一个更好的例子是检查天气是否通过连接发送了正确的数据。
@Dawngerpony 私有方法?这些应该避免。关闭http连接的方法应该是公开的。然后,您将对该方法进行单独的单元测试,该方法模拟连接并验证是否在其上调用了“关闭”。简单的。
r
raspacorp

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

希望能帮助到你。


我认为这是一种反模式。应该注入依赖项,期间。仅出于测试目的允许可选注入的依赖项是故意避免改进代码,并且是故意测试与生产中运行的代码不同的东西。这两件事都是可怕的,可怕的事情要做。
s
siulkilulki

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

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

构造函数注入 属性设置器注入 字段注入

docs 中的更多信息

下面是一个场注入的例子:

课程:

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

j
javaPlease42

使用 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();

    }

}

https://i.stack.imgur.com/gHdue.png


J
John B

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

这是 Mock construction of new objects 的链接


N
Nestor Milyaev

另一种简单的方法是向 bar.someMethod() 添加一些日志语句,然后确定您可以在执行测试时看到上述消息,请参见此处的示例:How to do a JUnit assert on a message in a logger

当您的 Bar.someMethod() 为 private 时,这尤其方便。


H
Haakon Løtveit

我今天遇到了这个问题,我不想使用 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());
}

这很简单,很容易看出发生了什么。当我想要的方法被调用时(在这里被嘲笑),做这些事情。其中包括对 AtomicInteger 的 incrementAndGet 的调用。您可以在这里使用 int[] ,但在我看来这不是很清楚。我们只是使用最终的东西,我们可以增加它。这是我们使用的 lambda 的限制。

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