Mockito: як перевірити метод викликався на об'єкті, створеному в межах методу?


322

Я новачок у Mockito.

З огляду на клас нижче, як я можу використовувати 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 SERVICE BUNDLE"); this.tracker = новий ServiceTracker (bc, HttpService.class.getName (), null) {@Override public Object addService (ServiceReference serviceRef) {httpService = (HttpService) super.addingService (serviceRef); registerServlets (); повернути httpService; }}}
ShAkKiR

Відповіді:


365

Ін'єкційна залежність

Якщо ви введете екземпляр 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. Якщо відповідь "так", то це детальна інформація про реалізацію, і ви не повинні турбуватися про тестування взаємодії спеціально (див. Відповідь @ Майкла). Якщо відповідь - ні, ви модифікуєте клас, тому що ваші труднощі при тестуванні - це червоний прапор, що ваш дизайн потребує невеликого вдосконалення (отже, я додав бонус про те, як дизайн диска TDD).
csturtz

3
Чи можете ви передати "реальний" об'єкт Mockito "verify"?
Джон Б

4
Ви також можете знущатися над фабрикою: BarFactory myFactory = mock(BarFactory.class); when(myFactory.createBar()).thenReturn(bar);
levsa

18

Класична відповідь: "Ви цього не робите". Ви перевіряєте загальнодоступний API Foo, а не його внутрішній.

Чи є поведінка Fooоб'єкта (або, що менш добре, якогось іншого об'єкта в навколишньому середовищі), на який впливає foo()? Якщо так, тестуйте це. А якщо ні, то що робить метод?


4
То що б ви насправді випробували тут? Загальнодоступний API - Fooце те public void foo(), де внутрішні дані пов'язані лише з барними елементами
бехеліт

15
Тестування лише загальнодоступного API є нормальним, поки не з’являться справжні помилки з побічними ефектами, які потребують тестів. Наприклад, перевірити, чи приватний метод належним чином закриває свої HTTP-з'єднання, є надмірним, поки ви не виявите, що приватний метод не закриває належним чином свої з'єднання, і, таким чином, викликає велику проблему. У цей момент Мокіто і verify()справді стане дуже корисним, навіть якщо ви більше не вклоняєтесь перед святим вівтарем інтеграційного тестування.
Dawngerpony

@DuffJ Я не використовую Java, але це звучить як щось, що повинен виявити ваш компілятор чи інструмент аналізу коду.
user247702

3
Я погоджуюся з DuffJ, хоча функціональне програмування - це весело, наступає момент, коли ваш код взаємодіє із зовнішнім світом. Неважливо, чи називаєте ви це "внутрішніми", "побічними ефектами" чи "функціональними", ви обов'язково хочете перевірити цю взаємодію: якщо це відбувається, і якщо це трапляється правильну кількість разів та з правильними аргументами. @Stijn: це міг бути поганим прикладом (але якщо декілька з'єднань слід відкрити, і лише деякі з них закриті, ніж це стане цікавим). Кращим прикладом може бути перевірка погоди, чи правильні дані були б надіслані через з'єднання.
Andras Balázs Lajtha

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, що, можливо, щось ви хочете.

Звичайно, недоліком є ​​те, що ви дозволяєте абоненту встановити смугу "Об'єкт".

Сподіваюся, це допомагає.


3
Я думаю, що це анти-закономірність. Залежності слід вводити, періодично. Дозвіл необов'язково введеної залежності виключно з метою тестування навмисно уникає поліпшення коду і навмисно випробовує щось інше, ніж код, який працює у виробництві. І те й інше - жахливі, жахливі речі.
ЕрікЕ

8

Рішення для прикладу коду за допомогою PowerMockito.whenNew

  • макетно-все 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 - це "правильна" відповідь.

Ось посилання на Mock будівництво нових об’єктів


0

Інший простий спосіб буде додати журнал заяву в bar.someMethod () , а потім встановити , ви можете побачити згадане повідомлення , коли ваш тест виконується, дивись приклади тут: Як зробити JUnit Assert на повідомлення в реєстраторі

Це особливо зручно, коли ваш Bar.someMethod () є private.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.