Mockito: wstrzykuj prawdziwe obiekty do prywatnych pól @Autowired

190

Używam Mockito @Mocki @InjectMocksadnotacji do wstrzykiwania zależności do prywatnych pól, które są opatrzone adnotacjami za pomocą Springa @Autowired:

@RunWith(MockitoJUnitRunner.class)
public class DemoTest {
    @Mock
    private SomeService service;

    @InjectMocks
    private Demo demo;

    /* ... */
}

i

public class Demo {

    @Autowired
    private SomeService service;

    /* ... */
}

Teraz chciałbym również wstrzykiwać prawdziwe obiekty do prywatnych @Autowiredpól (bez ustawiaczy). Czy to możliwe, czy mechanizm ogranicza się tylko do wstrzykiwania próbnych prób?

użytkownik2286693
źródło
5
Zwykle, gdy kpisz z rzeczy, oznacza to, że nie dbasz o konkretny obiekt; że tak naprawdę zależy ci tylko na zachowanie wyśmiewanego obiektu. Być może chcesz zamiast tego zrobić test integracyjny? A może mógłbyś podać uzasadnienie, dlaczego chcesz, aby drwione i konkretne przedmioty żyły razem?
Makoto
2
Cóż, mam do czynienia ze starszym kodem i zajęłoby dużo czasu, kiedy (...). Następnie Zwróć (...), aby skonfigurować próbkę, aby zapobiec niektórym NPE i tym podobnym. Z drugiej strony prawdziwy przedmiot może być bezpiecznie używany do tego celu. Byłoby więc bardzo przydatne, aby mieć możliwość wstrzykiwania prawdziwych obiektów wraz z próbkami. Nawet jeśli może to być zapach kodu, w tym konkretnym przypadku uważam to za rozsądne.
user2286693,
Nie zapomnij MockitoAnnotations.initMocks(this);o @Beforemetodzie. Wiem, że nie jest to bezpośrednio związane z pierwotnym pytaniem, ale dla każdego, kto przyjdzie później, należy to dodać, aby to uruchomić.
Cuga,
7
@Cuga: jeśli używasz Mockito dla JUnit, ( @RunWith(MockitoJUnitRunner.class)), nie potrzebujesz liniiMockitoAnnotations.initMocks(this);
Clint Eastwood
1
Dzięki - nigdy tego nie wiedziałem i zawsze określałem oba
Cuga

Odpowiedzi:

305

Użyj @Spyadnotacji

@RunWith(MockitoJUnitRunner.class)
public class DemoTest {
    @Spy
    private SomeService service = new RealServiceImpl();

    @InjectMocks
    private Demo demo;

    /* ... */
}

Mockito weźmie pod uwagę wszystkie pola posiadające @Mocklub @Spyadnotacje jako potencjalnych kandydatów do wstrzyknięcia do instancji opatrzone @InjectMocksadnotacją. W powyższym przypadku 'RealServiceImpl'instancja zostanie wstrzyknięta do „wersji demo”

Aby uzyskać więcej informacji, patrz

Mockito-home

@Szpieg

@Drwić

Dev Blanked
źródło
9
+1: Pracowałem dla mnie ... z wyjątkiem obiektów String. Mockito narzeka:Mockito cannot mock/spy following: - final classes - anonymous classes - primitive types
Adrian Pronk
Dzięki To też zadziałało dla mnie :) Aby proxy używało makiety do prawdziwej implementacji użyj szpiega
Swarit Agarwal
Dzięki! Właśnie tego potrzebowałem!
nterry
2
W moim przypadku Mockito nie wstrzykuje Szpiega. Jednak wstrzykuje próbkę. Pole jest prywatne i bez setera.
Vituel
8
BTW, nie ma takiej potrzeby new RealServiceImpl(), @Spy private SomeService service;tworzy prawdziwy obiekt za pomocą domyślnego konstruktora przed szpiegowaniem go.
parxier
20

Oprócz odpowiedzi @Dev Blanked, jeśli chcesz użyć istniejącej fasoli utworzonej przez Spring, kod można zmodyfikować do:

@RunWith(MockitoJUnitRunner.class)
public class DemoTest {

    @Inject
    private ApplicationContext ctx;

    @Spy
    private SomeService service;

    @InjectMocks
    private Demo demo;

    @Before
    public void setUp(){
        service = ctx.getBean(SomeService.class);
    }

    /* ... */
}

W ten sposób nie musisz zmieniać kodu (dodawać innego konstruktora), aby testy działały.

Yoaz Menda
źródło
2
@Aada Czy potrafisz opracować?
Yoaz Menda
1
Z której biblioteki to pochodzi, widzę tylko InjectMocks w org.mockito
Sameer
1
@sameer import org.mockito.InjectMocks;
Yoaz Menda
Dodanie komentarza w celu anulowania Aady. To zadziałało dla mnie. Nie mogłem „zwyczajnie nie używać wstrzykiwania pola”, jak sugerują inne odpowiedzi, więc musiałem się upewnić, że Autowiredpola z zależności zostały poprawnie utworzone.
Scrambo,
1
Próbowałem tego. ale otrzymuję wyjątek wskaźnika zerowego od metody instalacji
Akhil Surapuram
3

Mockito nie jest szkieletem DI, a nawet szkielety DI zachęcają do iniekcji konstruktora w porównaniu do iniekcji w terenie.
Dlatego deklarujesz konstruktor, aby ustawić zależności testowanej klasy:

@Mock
private SomeService serviceMock;

private Demo demo;

/* ... */
@BeforeEach
public void beforeEach(){
   demo = new Demo(serviceMock);
}

Używanie Mockito spyw ogólnej sprawie jest straszną wskazówką . Sprawia, że ​​klasa testowa jest krucha, a nie prosta i podatna na błędy: co tak naprawdę jest wyśmiewane? Co naprawdę jest testowane?
@InjectMocksa @Spytakże szkodzi ogólnemu projektowi, ponieważ zachęca rozdęte klasy i mieszane obowiązki w klasach.
Proszę przeczytać spy()javadoc przed użyciem go na ślepo (nacisk nie jest mój):

Tworzy szpiega prawdziwego obiektu. Szpieg wywołuje prawdziwe metody, chyba że są one stubowane. Prawdziwych szpiegów należy używać ostrożnie i od czasu do czasu , na przykład w przypadku starszego kodu.

Jak zwykle przeczytasz partial mock warning: Programowanie obiektowe rozwiązuje problem złożoności, dzieląc ją na osobne, specyficzne obiekty SRPy. Jak częściowa makieta pasuje do tego paradygmatu? Cóż, to po prostu nie… Częściowa makieta zwykle oznacza, że ​​złożoność została przeniesiona do innej metody na tym samym obiekcie. W większości przypadków nie jest to sposób projektowania aplikacji.

Jednak zdarzają się rzadkie przypadki, w których przydają się częściowe makiety: radzenie sobie z kodem, którego nie można łatwo zmienić (interfejsy stron trzecich, tymczasowe refaktoryzacja starszego kodu itp.) Jednak nie użyłbym częściowych makiet dla nowych, testowanych i dobrze zaprojektowany kod.

davidxxx
źródło
0

Na wiosnę istnieje specjalne narzędzie zwane ReflectionTestUtilsdo tego celu. Weź konkretną instancję i wstrzyknij w pole.


@Spy
..
@Mock
..

@InjectMock
Foo foo;

@BeforeEach
void _before(){
   ReflectionTestUtils.setField(foo,"bar", new BarImpl());// `bar` is private field
}
takacsot
źródło
-1

Wiem, że to stare pytanie, ale napotkaliśmy ten sam problem, próbując wstrzyknąć Strings. Wynaleźliśmy więc rozszerzenie JUnit5 / Mockito, które robi dokładnie to, co chcesz: https://github.com/exabrial/mockito-object-injection

EDYTOWAĆ:

@InjectionMap
 private Map<String, Object> injectionMap = new HashMap<>();

 @BeforeEach
 public void beforeEach() throws Exception {
  injectionMap.put("securityEnabled", Boolean.TRUE);
 }

 @AfterEach
 public void afterEach() throws Exception {
  injectionMap.clear();
 }
Jonathan S. Fisher
źródło