Je teste une application Spring Boot. J'ai plusieurs classes de test, chacune d'entre elles nécessitant un ensemble différent de beans simulés ou personnalisés.
Voici un croquis de la configuration:
src/main/Java:
package com.example.myapp;
@SpringBootApplication
@ComponentScan(
basePackageClasses = {
MyApplication.class,
ImportantConfigurationFromSomeLibrary.class,
ImportantConfigurationFromAnotherLibrary.class})
@EnableFeignClients
@EnableHystrix
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
package com.example.myapp.feature1;
@Component
public class Component1 {
@Autowired
ServiceClient serviceClient;
@Autowired
SpringDataJpaRepository dbRepository;
@Autowired
ThingFromSomeLibrary importantThingIDontWantToExplicitlyConstructInTests;
// methods I want to test...
}
src/test/Java:
package com.example.myapp;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = MyApplication.class)
@WebAppConfiguration
@ActiveProfiles("test")
public class Component1TestWithFakeCommunication {
@Autowired
Component1 component1; // <-- the thing we're testing. wants the above mock implementations of beans wired into it.
@Autowired
ServiceClient mockedServiceClient;
@Configuration
static class ContextConfiguration {
@Bean
@Primary
public ServiceClient mockedServiceClient() {
return mock(ServiceClient.class);
}
}
@Before
public void setup() {
reset(mockedServiceClient);
}
@Test
public void shouldBehaveACertainWay() {
// customize mock, call component methods, assert results...
}
}
package com.example.myapp;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = MyApplication.class)
@WebAppConfiguration
@ActiveProfiles("test")
public class Component1TestWithRealCommunication {
@Autowired
Component1 component1; // <-- the thing we're testing. wants the real implementations in this test.
@Autowired
ServiceClient mockedServiceClient;
@Before
public void setup() {
reset(mockedServiceClient);
}
@Test
public void shouldBehaveACertainWay() {
// call component methods, assert results...
}
}
Le problème avec la configuration ci-dessus est que l'analyse de composant configurée dans MyApplication prend Component1TestWithFakeCommunication.ContextConfiguration. Je reçois donc un faux ServiceClient, même dans Component1TestWithRealCommunication, où je veux la véritable implémentation de ServiceClient.
Bien que je puisse utiliser les constructeurs @Autowired et construire moi-même les composants dans les deux tests, il existe suffisamment d'éléments avec une configuration complexe pour que je préfère que Spring TestContext soit configuré pour moi (par exemple, référentiels Spring Data JPA, composants de bibliothèques en dehors de l'application qui tire les haricots du contexte Spring, etc.). L'imbrication d'une configuration Spring dans le test qui peut remplacer localement certaines définitions de bean dans le contexte Spring semble être une manière propre de le faire; le seul inconvénient est que ces configurations imbriquées finissent par affecter tous les tests Spring TestContext qui basent leur configuration sur MyApplication (le composant qui analyse le package d'application).
Comment modifier ma configuration de manière à toujours obtenir un contexte Spring "essentiellement réel" pour mes tests avec seulement quelques beans surchargés localement dans chaque classe de test?
Les éléments suivants devraient vous aider à atteindre votre objectif en introduisant un nouveau fake-communication
profile qui s'applique uniquement à la classe de test actuelle.
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = MyApplication.class)
@WebAppConfiguration
@ActiveProfiles({"test", "fake-communication"})
public class Component1TestWithFakeCommunication {
// @Autowired ...
@Profile("fake-communication")
@Configuration
static class ContextConfiguration {
@Bean
@Primary
public ServiceClient mockedServiceClient() {
return mock(ServiceClient.class);
}
}
}
Si vous avez un @SpringBootTest
, vous pouvez simplement annoter le service que vous voulez simuler avec @MockBean
. Aussi simple que cela.
Je voudrais faire quelques choses:
@ComponentScan
les soit.Component1TestWithFakeCommunication
, remplacez @SpringApplicationConfiguration(classes = MyApplication.class)
par @SpringApplicationConfiguration(classes = {MyApplication.class, Component1TestWithFakeCommunication.ContextConfiguration.class})
Cela devrait donner à Spring suffisamment d’informations pour simuler les beans de test, mais cela devrait également empêcher le runtime ApplicationContext
de remarquer vos beans de test.
Vous pouvez utiliser des profils explicites supplémentaires pour éviter de telles configurations de test (comme suggéré dans une autre réponse). Je l'ai aussi fait et j'ai même créé un support de bibliothèque pour cela.
Cependant, Spring-Boot est intelligent et dispose d’un "filtre de type" intégré pour résoudre ce problème automatiquement. Pour que cela fonctionne, vous devez supprimer votre annotation @ComponentScan
, qui retrouverait vos configurations de test, et laisser le @SpringBootApplication
faire le travail. Dans votre exemple, supprimez simplement ceci:
@SpringBootApplication
@ComponentScan(
basePackageClasses = {
MyApplication.class,
ImportantConfigurationFromSomeLibrary.class,
ImportantConfigurationFromAnotherLibrary.class})
et le remplacer par:
@SpringBootApplication(scanBasePackageClasses= {
MyApplication.class,
ImportantConfigurationFromSomeLibrary.class,
ImportantConfigurationFromAnotherLibrary.class})
Vous devrez peut-être également annoter votre test en tant que @SpringBootTest
. Cela devrait éviter d'analyser automatiquement les configurations (et composants) de classe interne, à l'exception de celles résidant dans le test current.