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

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

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

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

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

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

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

Ответы [ 7 ]

298 голосов
/ 23 марта 2012

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

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

18 голосов
/ 23 марта 2012

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

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

13 голосов
/ 19 декабря 2013

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

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

8 голосов
/ 11 апреля 2016

Я думаю, что Мокито @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();
    }
}
8 голосов
/ 12 января 2016

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

3 голосов
/ 23 марта 2012

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

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

0 голосов
/ 26 сентября 2018

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

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

...