it-roy-ru.com

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

Я новичок в Мокито.

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

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

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

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

где bar - это поддельный экземпляр Bar.

238
mre

Внедрение зависимости

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

273
csturtz

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

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

18
Michael Brewer-Davis

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

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

11
raspacorp

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

  • mockito-all 1.10.8
  • powermock-Core 1.6.1
  • powermock-module-junit4 1.6.1
  • powermock-api-mockito 1.6.1
  • джунит 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

8
javaPlease42

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

Да, если вы действительно хотите/должны это сделать, вы можете использовать PowerMock. Это следует считать последним средством. С PowerMock вы можете заставить его возвращать макет от вызова конструктору. Затем сделайте проверку на макете. Тем не менее, Csturtz это «правильный» ответ.

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

3
John B

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

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

0
Nestor Milyaev