Mockito: Injizieren Sie echte Objekte in private @ Autowired-Felder

190

Ich verwende Mockitos @Mockund @InjectMocksAnnotationen, um Abhängigkeiten in private Felder einzufügen, die mit Spring's kommentiert sind @Autowired:

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

    @InjectMocks
    private Demo demo;

    /* ... */
}

und

public class Demo {

    @Autowired
    private SomeService service;

    /* ... */
}

Jetzt möchte ich auch reale Objekte in private @AutowiredFelder einfügen (ohne Setter). Ist dies möglich oder beschränkt sich der Mechanismus nur auf das Injizieren von Mocks?

user2286693
quelle
5
Wenn Sie sich über Dinge lustig machen, bedeutet dies normalerweise, dass Sie sich nicht viel um das konkrete Objekt kümmern. dass Sie sich nur wirklich um das Verhalten des verspotteten Objekts kümmern. Vielleicht möchten Sie stattdessen einen Integrationstest durchführen? Oder könnten Sie eine Begründung dafür liefern, warum Sie verspottete und konkrete Objekte zusammenleben lassen möchten?
Makoto
2
Nun, ich habe es mit Legacy-Code zu tun, und es würde eine Menge von (...). ThenReturn (...) -Anweisungen erfordern, um den Mock einzurichten, nur um einige NPEs und dergleichen zu verhindern. Andererseits könnte ein reales Objekt dafür sicher verwendet werden. Es wäre also sehr praktisch, eine Option zu haben, um reale Objekte zusammen mit Mocks zu injizieren. Auch wenn dies ein Codegeruch sein mag, halte ich es in diesem speziellen Fall für vernünftig.
user2286693
Vergessen Sie nicht die MockitoAnnotations.initMocks(this);in der @BeforeMethode. Ich weiß, dass es nicht direkt mit der ursprünglichen Frage zusammenhängt, sondern mit jemandem, der später vorbeikommt, der hinzugefügt werden müsste, um diese Frage ausführbar zu machen.
Cuga
7
@Cuga: Wenn Sie den Mockito-Läufer für JUnit ( @RunWith(MockitoJUnitRunner.class)) verwenden, brauchen Sie die Linie nichtMockitoAnnotations.initMocks(this);
Clint Eastwood
1
Danke - das habe ich nie gewusst und habe immer beides angegeben
Cuga

Antworten:

304

Verwenden Sie @SpyAnmerkungen

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

    @InjectMocks
    private Demo demo;

    /* ... */
}

Mockito betrachtet alle Felder mit @Mockoder mit @SpyAnmerkungen als potenzielle Kandidaten, die in die mit @InjectMocksAnmerkungen versehene Instanz eingefügt werden sollen. Im obigen Fall wird die 'RealServiceImpl'Instanz in die 'Demo' eingefügt.

Weitere Einzelheiten finden Sie unter

Mockito-Zuhause

@Spion

@Spotten

Dev Blanked
quelle
9
+1: Hat für mich funktioniert ... außer für String-Objekte. Mockito beschwert sich:Mockito cannot mock/spy following: - final classes - anonymous classes - primitive types
Adrian Pronk
Danke, es hat auch bei mir funktioniert :) Um den Proxy für die echte Implementierung zu verspotten, benutze spy
Swarit Agarwal
Vielen Dank! Das ist genau das, was ich brauchte!
nterry
2
In meinem Fall injiziert Mockito keinen Spion. Es spritzt allerdings einen Mock. Das Feld ist privat und ohne Setter.
Vituel
8
BTW, gibt es keine Notwendigkeit zu new RealServiceImpl(), @Spy private SomeService service;erzeugt ein reales Objekt Standardkonstruktors verwenden , bevor sowieso auf sie Spionage.
Parxier
20

Wenn Sie zusätzlich zu der Antwort @Dev Blanked eine vorhandene Bean verwenden möchten, die von Spring erstellt wurde, kann der Code wie folgt geändert werden:

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

    /* ... */
}

Auf diese Weise müssen Sie Ihren Code nicht ändern (einen weiteren Konstruktor hinzufügen), damit die Tests funktionieren.

Yoaz Menda
quelle
2
@Aada Kannst du das näher erläutern?
Yoaz Menda
1
Aus welcher Bibliothek kommt das? Ich kann InjectMocks nur in org.mockito sehen
Sameer
1
@sameer import org.mockito.InjectMocks;
Yoaz Menda
Hinzufügen eines Kommentars zum Aufheben von Aada's. Das hat bei mir funktioniert. Ich konnte "einfach keine Feldinjektion verwenden", wie in anderen Antworten vorgeschlagen, und musste daher sicherstellen, dass die AutowiredFelder aus den Abhängigkeiten korrekt erstellt wurden.
Scrambo
1
Ich habe es versucht. Aber ich
erhalte
3

Mockito ist kein DI-Framework, und selbst DI-Frameworks fördern Konstruktorinjektionen gegenüber Feldinjektionen.
Sie deklarieren also einfach einen Konstruktor, um Abhängigkeiten der zu testenden Klasse festzulegen:

@Mock
private SomeService serviceMock;

private Demo demo;

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

Die Verwendung von Mockito spyfür den allgemeinen Fall ist ein schrecklicher Rat. Es macht die Testklasse spröde, nicht gerade und fehleranfällig: Was wird wirklich verspottet? Was ist wirklich getestet?
@InjectMocksund @Spyschadet auch dem Gesamtdesign, da es aufgeblähte Klassen und gemischte Verantwortlichkeiten in den Klassen fördert.
Bitte lesen Sie das spy()Javadoc, bevor Sie es blind verwenden (die Betonung liegt nicht bei mir):

Erstellt einen Spion des realen Objekts. Der Spion ruft echte Methoden auf, es sei denn, sie werden gestoppt. Echte Spione sollten sorgfältig und gelegentlich eingesetzt werden , beispielsweise beim Umgang mit Legacy-Code.

Wie üblich lesen Sie partial mock warningFolgendes: Objektorientierte Programmierung befasst sich mit Komplexität, indem Sie die Komplexität in separate, spezifische SRPy-Objekte aufteilen. Wie passt partielles Mock in dieses Paradigma? Nun, das tut es einfach nicht ... Partial Mock bedeutet normalerweise, dass die Komplexität auf eine andere Methode für dasselbe Objekt verschoben wurde. In den meisten Fällen möchten Sie Ihre Anwendung auf diese Weise nicht entwerfen.

Es gibt jedoch seltene Fälle, in denen Teil-Mocks nützlich sind: Umgang mit Code, den Sie nicht einfach ändern können (Schnittstellen von Drittanbietern, vorläufiges Refactoring von Legacy-Code usw.) Ich würde jedoch Teil-Mocks nicht für neue, testgetriebene und gut funktionierende Mocks verwenden. entworfener Code.

davidxxx
quelle
0

Im Frühjahr gibt es ein spezielles Dienstprogramm ReflectionTestUtilsfür diesen Zweck. Nehmen Sie die spezifische Instanz und injizieren Sie sie ins Feld.


@Spy
..
@Mock
..

@InjectMock
Foo foo;

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

Ich weiß, dass dies eine alte Frage ist, aber wir hatten das gleiche Problem, als wir versuchten, Strings zu injizieren. Deshalb haben wir eine JUnit5 / Mockito-Erweiterung erfunden, die genau das tut, was Sie wollen: https://github.com/exabrial/mockito-object-injection

BEARBEITEN:

@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
quelle