J'ai une application Spring Boot 1.4.2. Un code utilisé au démarrage ressemble à ceci:
@Component
class SystemTypeDetector{
public enum SystemType{ TYPE_A, TYPE_B, TYPE_C }
public SystemType getSystemType(){ return ... }
}
@Component
public class SomeOtherComponent{
@Autowired
private SystemTypeDetector systemTypeDetector;
@PostConstruct
public void startup(){
switch(systemTypeDetector.getSystemType()){ // <-- NPE here in test
case TYPE_A: ...
case TYPE_B: ...
case TYPE_C: ...
}
}
}
Il existe un composant qui détermine le type de système. Ce composant est utilisé lors du démarrage à partir d'autres composants. En production, tout fonctionne bien.
Maintenant, je veux ajouter des tests d'intégration en utilisant @MockBean
De Spring 1.4.
Le test ressemble à ceci:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MyWebApplication.class, webEnvironment = RANDOM_PORT)
public class IntegrationTestNrOne {
@MockBean
private SystemTypeDetector systemTypeDetectorMock;
@Before
public void initMock(){
Mockito.when(systemTypeDetectorMock.getSystemType()).thenReturn(TYPE_C);
}
@Test
public void testNrOne(){
// ...
}
}
Fondamentalement, la moquerie fonctionne bien. Mon systemTypeDetectorMock est utilisé et si j'appelle getSystemType
-> TYPE_C
Est retourné.
Le problème est que l'application ne démarre pas. Actuellement, le bon fonctionnement semble être:
Mon problème est que l'application démarre avec une maquette non initialisée. L'appel à getSystemType()
renvoie donc null.
Ma question est: comment puis-je configurer les maquettes avant le démarrage de l'application?
Edit: Si quelqu'un a le même problème, une solution de contournement consiste à utiliser @MockBean(answer = CALLS_REAL_METHODS)
. Cela appelle le vrai composant et dans mon cas, le système démarre. Après le démarrage, je peux changer le comportement factice.
Dans ce cas, vous devez configurer les maquettes d'une manière que nous avions l'habitude de faire avant @MockBean
a été introduit - en spécifiant manuellement un @Primary
bean qui remplacera l'original dans le contexte.
@SpringBootTest
class DemoApplicationTests {
@TestConfiguration
public static class TestConfig {
@Bean
@Primary
public SystemTypeDetector mockSystemTypeDetector() {
SystemTypeDetector std = mock(SystemTypeDetector.class);
when(std.getSystemType()).thenReturn(TYPE_C);
return std;
}
}
@Autowired
private SystemTypeDetector systemTypeDetector;
@Test
void contextLoads() {
assertThat(systemTypeDetector.getSystemType()).isEqualTo(TYPE_C);
}
}
Puisque @TestConfiguration
classe est une classe interne statique, elle ne sera choisie automatiquement que par ce test. Comportement simulé complet que vous mettriez dans @Before
doit être déplacé vers une méthode qui initialise un bean.
Ce que vous utilisez est bon pour un test unitaire:
org.mockito.Mockito#when()
Essayez d'utiliser les méthodes suivantes pour vous moquer des haricots de printemps lorsque le contexte est défini:
org.mockito.BDDMockito#given()
Si vous utilisez @SpyBean, alors vous devez utiliser une autre syntaxe:
willReturn(Arrays.asList(val1, val2))
.given(service).getEntities(any());
L'initialisation de Spring est déclenchée avant @Before
L'annotation de Mockito pour que la maquette ne soit pas initialisée au moment où le @PostConstruct
la méthode annotée est exécutée.
Essayez de "retarder" la détection de votre système à l'aide de @Lazy
annotation sur le composant SystemTypeDetector
. Utilisez votre SystemTypeDetector
là où vous en avez besoin, gardez à l'esprit que vous ne pouvez pas déclencher cette détection dans un @PostConstruct
ou crochet équivalent.