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

322

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

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

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

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

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

где barнасмешливый экземпляр Bar.

MRE
источник
2
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 addService (ServiceReference serviceRef) {httpService = (HttpService) super.addingService (serviceRef); registerServlets (); вернуть httpService; }}}
ShAkKiR

Ответы:

366

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

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

csturtz
источник
6
Есть ли способ сделать это без изменения класса для модульного тестирования?
Мр
6
Bar bar = mock(Bar.class)вместоBar bar = new Bar();
Джон Б.
7
не то, чтобы я знал. но я не предлагаю вам изменять класс только для модульного тестирования. Это действительно разговор о чистом коде и SRP. Или ... это ответственность метода foo () в классе Foo за создание объекта Bar. Если ответ «да», то это детали реализации, и вам не стоит беспокоиться о тестировании взаимодействия специально (см. Ответ @ Michael). Если ответ «нет», то вы изменяете класс, потому что ваша трудность в тестировании заключается в том, что ваш дизайн нуждается в небольшом улучшении (отсюда и бонус, который я добавил, о том, как TDD управляет дизайном).
csturtz
3
Можете ли вы передать «реальный» объект в «подтверждение» Мокито?
Джон Б
4
Вы также можете издеваться над фабрикой: BarFactory myFactory = mock(BarFactory.class); when(myFactory.createBar()).thenReturn(bar);
Левса
18

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

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

Майкл Брюер-Дэвис
источник
4
Так что бы вы на самом деле тестировали здесь? Публичный API из FooIS public void foo(), где внутренность только BAR связана.
behelit
15
Тестирование только общедоступного API - это хорошо, пока не появятся подлинные ошибки с побочными эффектами, которые требуют тестирования. Например, проверка того, что закрытый метод правильно закрывает свои HTTP-соединения, является излишней, пока вы не обнаружите, что закрытый метод не закрывает свои соединения должным образом и, следовательно, вызывает огромную проблему. В этот момент Mockito и verify()станет действительно очень полезным, даже если вы больше не поклоняетесь на святом алтаре интеграционного тестирования.
Dawngerpony
@DuffJ Я не использую Java, но это звучит как то, что должен обнаружить ваш компилятор или инструмент анализа кода.
user247702
3
Я согласен с DuffJ, хотя функциональное программирование - это весело, наступает момент, когда ваш код взаимодействует с внешним миром. Неважно, если вы называете это «внутренними», «побочными эффектами» или «функциональностью», вы определенно хотите проверить это взаимодействие: если это происходит, и происходит ли оно правильное число раз и с правильными аргументами. @Stijn: это мог бы быть плохой пример (но если нужно открыть несколько соединений и закрыть только некоторые из них, это будет интересно). Лучшим примером будет проверка правильности данных, которые будут отправлены через соединение.
Андрас Балаш Лайта
13

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

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

raspacorp
источник
3
Я думаю, что это анти-шаблон. Зависимости должны быть введены, точка. Разрешение необязательно внедренной зависимости исключительно для целей тестирования - это намеренное предотвращение улучшения кода и намеренное тестирование чего-то отличного от кода, который выполняется в рабочей среде. Оба эти ужасные, ужасные вещи.
ErikE
8

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

javaPlease42
источник
8

Я думаю, что Мокито @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();
    }
}
siulkilulki
источник
3

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

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

Джон Б
источник
0

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

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

Нестор Миляев
источник