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


322

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

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

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

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

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

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


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


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.

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


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 выход


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

3

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

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


0

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.