web-dev-qa-db-fra.com

Comment utiliser @ComponentScan avec les configurations de contexte spécifiques au test dans SpringJunit4TestRunner?

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?

14
Jonathan Fuerth

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);
        }
    }
}
9
Sam Brannen

Si vous avez un @SpringBootTest, vous pouvez simplement annoter le service que vous voulez simuler avec @MockBean. Aussi simple que cela.

3
Jorge Viana

Je voudrais faire quelques choses:

  1. Déplacez vos classes de test dans un package différent pour éviter que @ComponentScan les soit.
  2. Dans 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.

0
Josh Ghiloni

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.

0
Marwin