Mockito: iniettare oggetti reali nei campi privati ​​@Autowired


191

Sto usando Mockito @Mocke @InjectMocksannotazioni per iniettare dipendenze in campi privati ​​che sono annotati con quelli di Spring @Autowired:

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

    @InjectMocks
    private Demo demo;

    /* ... */
}

e

public class Demo {

    @Autowired
    private SomeService service;

    /* ... */
}

Ora vorrei anche iniettare oggetti reali in @Autowiredcampi privati (senza setter). È possibile o il meccanismo si limita all'iniezione di Mock?


5
Normalmente quando deridi le cose, implica che non ti importa molto dell'oggetto concreto; che ti interessa davvero solo il comportamento dell'oggetto deriso. Forse vuoi fare un test di integrazione invece? Oppure potresti fornire una spiegazione logica del motivo per cui vuoi che oggetti derisi e concreti vivano insieme?
Makoto,

2
Bene, ho a che fare con il codice legacy e ci vorrebbe un sacco di quando (...). Quindi Return (...) istruzioni per impostare il finto solo per impedire alcuni NPE e simili. D'altra parte, un oggetto reale potrebbe essere usato in sicurezza per questo. Quindi sarebbe molto utile avere un'opzione per iniettare oggetti reali insieme a beffe. Anche se questo può essere un odore di codice, lo considero ragionevole in questo caso particolare.
user2286693,

Non dimenticare MockitoAnnotations.initMocks(this);il @Beforemetodo. So che non è direttamente correlato alla domanda originale, ma a chiunque arrivi più tardi, che sarebbe necessario aggiungere per rendere questo eseguibile.
Cuga,

7
@Cuga: se usi il corridore Mockito per JUnit, ( @RunWith(MockitoJUnitRunner.class)), non hai bisogno della lineaMockitoAnnotations.initMocks(this);
Clint Eastwood

1
Grazie, non l'ho mai saputo e ho sempre specificato entrambi
Cuga,

Risposte:


306

Usa @Spyannotazione

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

    @InjectMocks
    private Demo demo;

    /* ... */
}

Mockito considererà tutti i campi che hanno @Mocko @Spyannotazioni come potenziali candidati da iniettare nell'istanza annotata con @InjectMocksannotazione. Nel caso sopra'RealServiceImpl' istanza verrà iniettata nella 'demo'

Per maggiori dettagli fare riferimento

Mockito-casa

@Spiare

@Mock


9
+1: ha funzionato per me ... ad eccezione degli oggetti String. Mockito si lamenta:Mockito cannot mock/spy following: - final classes - anonymous classes - primitive types
Adrian Pronk,

Grazie Ha funzionato anche per me :) Per fare in modo che il proxy usi finto altro per una vera implementazione usa la spia
Swarit Agarwal,

Grazie! Questo è esattamente quello di cui avevo bisogno!
nterry,

2
Nel mio caso, Mockito non sta iniettando una spia. Inietta un Mock però. Il campo è privato e senza setter.
Vituel,

8
A proposito, non è necessario new RealServiceImpl(), @Spy private SomeService service;crea un oggetto reale usando il costruttore predefinito prima di spiarlo comunque.
parxier,

20

In aggiunta alla risposta @Dev Blanked, se si desidera utilizzare un bean esistente creato da Spring, il codice può essere modificato in:

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

    /* ... */
}

In questo modo non è necessario modificare il codice (aggiungere un altro costruttore) solo per far funzionare i test.


2
@Aada Puoi elaborare?
Yoaz Menda,

1
Da quale libreria proviene, vedo solo InjectMocks in org.mockito
Sameer,

1
@sameer import org.mockito.InjectMocks;
Yoaz Menda,

Aggiunta di un commento per cancellare Aada. Questo ha funzionato per me. Non potevo "semplicemente non usare l'iniezione di campo" come suggerito da altre risposte, quindi dovevo assicurarmi che i Autowiredcampi delle dipendenze fossero creati correttamente.
Scrambo,

1
Ci ho provato ma sto ricevendo un'eccezione del puntatore null dal metodo di installazione
Akhil Surapuram

3

Mockito non è un framework DI e persino i framework DI incoraggiano le iniezioni del costruttore rispetto alle iniezioni sul campo.
Quindi devi semplicemente dichiarare un costruttore per impostare le dipendenze della classe sotto test:

@Mock
private SomeService serviceMock;

private Demo demo;

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

L'uso di Mockito spynel caso generale è un consiglio terribile. Rende la classe di test fragile, non diritta e soggetta a errori: cosa è veramente deriso? Cosa è veramente testato?
@InjectMockse @Spydanneggia anche il design generale poiché incoraggia le classi gonfie e le responsabilità miste nelle classi.
Si prega di leggere il spy()javadoc prima di usarlo alla cieca (l'enfasi non è mia):

Crea una spia dell'oggetto reale. La spia chiama metodi reali a meno che non vengano stubati. Le vere spie dovrebbero essere usate con attenzione e occasionalmente , ad esempio quando si tratta di codice legacy.

Come al solito leggerai il partial mock warning : La programmazione orientata agli oggetti affronta la complessità dividendola in oggetti separati, specifici, SRPy. Come si inserisce la derisione parziale in questo paradigma? Bene, semplicemente non ... La derisione parziale di solito significa che la complessità è stata spostata in un metodo diverso sullo stesso oggetto. Nella maggior parte dei casi, non è questo il modo in cui si desidera progettare l'applicazione.

Tuttavia, ci sono rari casi in cui le simulazioni parziali sono utili: gestire il codice che non è possibile modificare facilmente (interfacce di terze parti, refactoring provvisorio di codice legacy, ecc.) Tuttavia, non utilizzerei le simulazioni parziali per nuove, guidate dai test e ben- codice progettato.


0

In primavera c'è un'utilità dedicata chiamata ReflectionTestUtilsper questo scopo. Prendi l'istanza specifica e immetti nel campo.


@Spy
..
@Mock
..

@InjectMock
Foo foo;

@BeforeEach
void _before(){
   ReflectionTestUtils.setField(foo,"bar", new BarImpl());// `bar` is private field
}

-1

So che questa è una vecchia domanda, ma abbiamo dovuto affrontare lo stesso problema quando provavamo a iniettare stringhe. Quindi abbiamo inventato un'estensione JUnit5 / Mockito che fa esattamente quello che vuoi: https://github.com/exabrial/mockito-object-injection

MODIFICARE:

@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();
 }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.