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

Я новичок в Mockito.

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

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

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

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

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

Ответ 1

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

Если вы добавляете экземпляр Bar или factory, который используется для создания экземпляра Bar (или один из других 483 способов сделать это), у вас будет доступ, необходимый для выполнения теста.

Factory Пример:

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

Ответ 2

Классический ответ: "У вас нет". Вы проверяете открытый API Foo, а не его внутренние элементы.

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

Ответ 3

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

Надеюсь, что это поможет.

Ответ 4

Решение для вашего примера кода с помощью 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

Ответ 5

Я думаю, что 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();
    }
}

Ответ 6

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

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

Ответ 7

Другим простым способом было бы добавить некоторый оператор журнала в bar.someMethod(), а затем убедиться, что вы можете видеть указанное сообщение, когда ваш тест выполнен, см. Примеры здесь: Как сделать JUnit assert в сообщении в журнале

Это особенно удобно, когда ваш Bar.someMethod() является private.