Mockito, JUnit i Spring

Zacząłem uczyć się o Mockito dopiero dzisiaj. Napisałem prosty test (z Junitem, patrz niżej), ale nie wiem jak mogę użyć mock object wewnątrz fasoli Springa. Jaka jest najlepsza praktyka w pracy ze sprężyną. Jak wstrzyknąć lek do fasoli?

Możesz pominąć to do z powrotem do mojego pytania .

Po pierwsze, czego się nauczyłem. Jest to bardzo dobry artykuł Mocks are 'T Stubs który wyjaśnia podstawy (Mock' s sprawdza weryfikacja zachowania Nie weryfikacja stanu ). To jest dobry przykład tutaj Mockito Tutaj łatwiej wyśmiewać się z mockito mamy wyjaśnienie, że Mockito to zarówno mock , jak i stuby .

Tutaj Mockito i tutaj Matchers znajdziesz więcej przykładów.

Ten test

@Test
public void testReal(){
    List<String> mockedList = mock(List.class);
     //stubbing
     //when(mockedList.get(0)).thenReturn("first");

    mockedList.get(anyInt());
    OngoingStubbing<String> stub= when(null);
    stub.thenReturn("first");

    //String res = mockedList.get(0);
                //System.out.println(res);

     //you can also verify using argument matcher
     //verify(mockedList).get(anyInt());

    verify(mockedList);
    mockedList.get(anyInt());
}

Działa dobrze.

Wracając do mojego pytania.Tutaj Wstrzykiwanie Mockito KPI ze Spring bean ktoś próbuje użyć Spring ReflectionTestUtils.setField(), ale tutaj Spring testy integracyjne, tworząc makiety obiektów{[20] } mamy zalecenie, aby zmienić kontekst Spring.

Nie bardzo rozumiem ostatnie dwa linki... Czy ktoś może mi wyjaśnić jaki problem ma Wiosna z Mockito? Co jest nie tak z tym rozwiązaniem?

@InjectMocks
private MyTestObject testObject

@Mock
private MyDependentObject mockedObject

@Before
public void setup() {
        MockitoAnnotations.initMocks(this);
}

Https://stackoverflow.com/a/8742745/1137529

EDIT : nie byłem czysto. Podam 3 przykłady kodu, aby wyjaśnić moje ja: Załóżmy, że mamy bean HelloWorld z metodą printHello() i bean HelloFacade z metodą sayHello, które przekierowują wywołania do metody HelloWorld printHello().

Pierwszy przykład to użycie kontekstu Springa i bez niestandardowego biegacza, użycie Reflectestutils do iniekcji zależności (DI):
public class Hello1Test  {
private ApplicationContext ctx;

@Before
public void setUp() {
    MockitoAnnotations.initMocks(this);
    this.ctx = new ClassPathXmlApplicationContext("META-INF/spring/ServicesImplContext.xml");
}



@Test
public void testHelloFacade() {
    HelloFacade obj = (HelloFacade) ctx.getBean(HelloFacadeImpl.class);
    HelloWorld mock = mock(HelloWorld.class);
    doNothing().when(mock).printHello();

    ReflectionTestUtils.setField(obj, "hello", mock);
    obj.sayHello();

    verify(mock, times(1)).printHello();
}

}

Jak zauważył @Noam jest sposób, aby uruchomić go bez wyraźnego wywołania do MockitoAnnotations.initMocks(this);. Pozwolę sobie również na wykorzystanie kontekstu wiosny w tej przykład.

@RunWith(MockitoJUnitRunner.class)
public class Hello1aTest {


@InjectMocks
private HelloFacade obj =  new HelloFacadeImpl();

@Mock
private HelloWorld mock;


@Test
public void testHelloFacade() {
    doNothing().when(mock).printHello();
    obj.sayHello();
}

}

Inny sposób na to

public class Hello1aTest {

@Before
public void setUp() {
    MockitoAnnotations.initMocks(this);
}


@InjectMocks
private HelloFacadeImpl obj;

@Mock
private HelloWorld mock;


@Test
public void testHelloFacade() {
    doNothing().when(mock).printHello();
    obj.sayHello();
}

}

Noth, że w preivious przykład musimy ręcznie zainstalować HelloFacadeImpl i przypisać go do HelloFacade, ponieważ HelloFacade jest interfejsem. W ostatnim przykładzie możemy po prostu zadeklarować HelloFacadeImpl, a Mokito utworzy go za nas. Wadą tego podejścia jest to, że teraz Jednostka-under-test jest klasą impl, a nie interfejsem.

Author: Community, 2012-06-06

7 answers

Szczerze mówiąc nie jestem pewien, czy naprawdę rozumiem twoje pytanie :P postaram się wyjaśnić jak najwięcej, z tego, co mam z twojego pierwotnego pytania:

Po pierwsze, w większości przypadków nie należy martwić się o wiosnę. Rzadko trzeba mieć springa zaangażowanego w pisanie testu jednostkowego. W normalnym przypadku, wystarczy utworzyć instancję testowanego systemu (SUT, cel, który ma być testowany) w teście jednostkowym, i wstrzyknąć zależności SUT w teście zbyt. Zależności są zwykle mock/stub.

Twój oryginalny sugerowany sposób, a Przykład 2, 3 dokładnie robi to, co opisuję powyżej.

W niektórych rzadkich przypadkach (takich jak testy integracyjne lub specjalne testy jednostkowe), musisz utworzyć kontekst aplikacji Spring i pobrać swój SUT z kontekstu aplikacji. W takim przypadku wierzę, że możesz:

[[2]} 1) Utwórz swój SUT w spring app ctx, uzyskaj odniesienie do niego i wstrzyknij do niego kpiny
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("test-app-ctx.xml")
public class FooTest {

    @Autowired
    @InjectMocks
    TestTarget sut;

    @Mock
    Foo mockFoo;

    @Before
    /* Initialized mocks */
    public void setup() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void someTest() {
         // ....
    }
}

Lub

2) postępuj zgodnie ze sposobem opisanym w linku Wiosna Testy Integracyjne, Tworzenie Makiet Obiektów . To podejście polega na tworzeniu moków w kontekście aplikacji Springa, a mock można uzyskać z aplikacji CTX, aby wykonać Stub/weryfikację:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("test-app-ctx.xml")
public class FooTest {

    @Autowired
    TestTarget sut;

    @Autowired
    Foo mockFoo;

    @Test
    public void someTest() {
         // ....
    }
}
Oba sposoby powinny działać. Główną różnicą jest to, że w pierwszym przypadku będą wstrzykiwane zależności po przejściu przez cykl życia sprężyny itp. (np. inicjalizacja fasoli), podczas gdy ten drugi przypadek jest wstrzykiwany. Na przykład, jeśli twój SUT implementuje initializingbean springa, a procedura inicjalizacji obejmuje zależności, zobaczysz różnicę między tymi dwoma podejściami. Wierzę, że nie ma dobra lub zła dla tych 2 podejść, tak długo, jak wiesz, co robisz.

Tylko dodatek, @Mock, @ Inject, MocktoJunitRunner itp. są niepotrzebne w użyciu Mockito. Są to tylko narzędzia, aby zaoszczędzić na pisaniu Mockito.mock(Foo.klasy) i kilka inwokacji setera.

 49
Author: Adrian Shum,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-27 11:01:10

Twoje pytanie wydaje się być pytaniem o to, który z trzech przykładów podałeś jest preferowanym podejściem.

Przykład 1 używanie testu odbicia nie jest dobrym podejściem do testów jednostkowych. Naprawdę nie chcesz w ogóle ładować kontekstu sprężynowego do testu jednostkowego. Po prostu mock i wstrzyknąć to, co jest wymagane, jak pokazano w innych przykładach.

Chcesz załadować kontekst sprężyny, jeśli chcesz wykonać testy integracyjne, jednak Wolałbym użyć @RunWith(SpringJUnit4ClassRunner.class), aby wykonać Ładowanie kontekstu wraz z @Autowired, jeśli potrzebujesz jawnie dostępu do jego fasoli.

Przykład 2 jest poprawnym podejściem, a użycie @RunWith(MockitoJUnitRunner.class) usunie konieczność podania metody @Before i jawnego wywołania MockitoAnnotations.initMocks(this);

Przykład 3 {[10] } jest kolejnym poprawnym podejściem, które nie używa @RunWith(...). Nie utworzyłeś jawnie swojej klasy w teście HelloFacadeImpl, ale mogłeś zrobić to samo w przykładzie 2.

Mój sugerujemy użycie przykładu 2 do testowania jednostek, ponieważ zmniejsza to bałagan w kodzie. Możesz wrócić do bardziej szczegółowej konfiguracji, jeśli i kiedy jesteś do tego zmuszony.

 6
Author: Brad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-07 00:17:52

[2]}Wprowadzenie nowych urządzeń testowych wiosną 4.2.RC1 pozwala pisać testy integracyjne, które nie opierają się na SpringJUnit4ClassRunner. Sprawdź część dokumentacji.

W Twoim przypadku mógłbyś napisać swój wiosenny test integracyjny i nadal używać moków takich jak:

@RunWith(MockitoJUnitRunner.class)
@ContextConfiguration("test-app-ctx.xml")
public class FooTest {

    @ClassRule
    public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();

    @Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();

    @Autowired
    @InjectMocks
    TestTarget sut;

    @Mock
    Foo mockFoo;

    @Test
    public void someTest() {
         // ....
    }
}
 4
Author: geoand,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-26 15:01:22

Naprawdę nie potrzebujesz MockitoAnnotations.initMocks(this); Jeśli używasz mockito 1.9 (lub nowszego ) - wystarczy to:

@InjectMocks
private MyTestObject testObject;

@Mock
private MyDependentObject mockedObject;

Adnotacja @InjectMocks wprowadzi wszystkie Twoje mocki do MyTestObject obiektu.

 3
Author: Noam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-06 08:07:57

Oto moje krótkie podsumowanie.

Jeśli chcesz napisać test jednostkowy, nie używaj Spring applicationContext, ponieważ nie chcesz, aby jakiekolwiek rzeczywiste zależności były wstrzykiwane w klasie, którą testujesz jednostkowo. Zamiast tego użyj mocks, albo z adnotacją @RunWith(MockitoJUnitRunner.class) na górze klasy, albo z MockitoAnnotations.initMocks(this) w metodzie @Before.

Jeśli chcesz napisać test integracyjny, użyj:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("yourTestApplicationContext.xml")

Aby skonfigurować kontekst aplikacji na przykład z bazą danych w pamięci. Normalnie nie używasz moków w testy integracyjne, ale można to zrobić za pomocą metody MockitoAnnotations.initMocks(this) opisanej powyżej.

 2
Author: Adriaan Koster,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-27 09:50:59

Różnica w tym, czy musisz utworzyć instancję swojego @InjectMocks przypisanego pola jest w wersji Mockito, a nie w tym, czy używasz MockitoJunitRunner czy MockitoAnnotations.initMocks. W wersji 1.9, która będzie również obsługiwać pewne wtryski konstruktorów twoich pól @Mock, zrobi instancję za Ciebie. We wcześniejszych wersjach musisz utworzyć instancję samodzielnie.

W ten sposób przeprowadzam jednostkowe testy fasoli wiosennej. Nie ma problemu. Ludzie wpadają w zamęt, gdy chcą wykorzystać wiosnę pliki konfiguracyjne, aby faktycznie wykonać iniekcję mocks, która przekracza punkt testów jednostkowych i testów integracyjnych.

I oczywiście badaną jednostką jest Impl. Musisz przetestować konkretną rzecz, prawda? Nawet jeśli zadeklarujesz go jako interfejs, będziesz musiał utworzyć instancję prawdziwego, aby go przetestować. Teraz, Można dostać się do szpiegów, które są stub / makiety okładki wokół prawdziwych obiektów, ale to powinno być dla narożnych przypadkach.

 0
Author: jhericks,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-06 22:26:11

Jeśli chcesz przenieść swój projekt do Spring Boot 1.4, możesz użyć nowej adnotacji @MockBean do udawania MyDependentObject. Dzięki tej funkcji możesz usunąć adnotacje Mockito @Mock i @InjectMocks z testu.

 0
Author: luboskrnac,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-05 13:30:07