Mockito: как проверить, что метод был вызван для объекта, созданного в методе?

Я новичок в Mockito.

учитывая класс ниже, как я могу использовать Mockito для проверки этого someMethod был вызван ровно один раз после ?

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

Я хотел бы сделать следующий вызов проверки,

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

здесь bar является издевательским экземпляром Bar.

7 ответов


Инъекции Зависимостей

Если вы вводите экземпляр 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 может управляйте дизайном своего кода.


Foo, а не его внутренности.

есть ли поведение Foo объект (или, менее хороший, какой-либо другой объект в среде), на который влияет foo()? Если да, проверьте это. А если нет, то что делает метод?


Если вы не хотите использовать 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);
   }
}

как вы можете видеть при вызове метода таким образом, вам не нужно импортировать класс Bar в любой другой класс, который вызывает ваш метод foo, который, возможно, Вам нужен.

конечно, недостатком является то, что вы позволяете вызывающему установить объект Bar.

надеюсь, что это помогает.


решение для вашего примера кода с помощью PowerMockito.whenNew

  • mockito-все 1.10.8
  • powermock-core 1.6.1
  • powermock-модуль-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


Я думаю, 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
    }
}

тест:

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

да, если вы действительно хотите / должны сделать это, вы можете использовать PowerMock. Это следует рассматривать как последнее средство. С PowerMock вы можете заставить его вернуть макет из вызова конструктору. Затем сделайте проверку на макете. Тем не менее, csturtz-это "правильный" ответ.

вот ссылка макет строительства новых объектов


другим простым способом было бы добавить некоторую инструкцию log в панель.someMethod (), а затем убедитесь, что вы можете увидеть указанное сообщение при выполнении теста, см. примеры здесь: Как сделать JUnit assert на сообщение в регистраторе

это особенно удобно, когда ваш бар.someMethod () is private.