web-dev-qa-db-fra.com

Remplacement de beans dans les tests d'intégration

Pour mon application Spring-Boot, je fournis un RestTemplate via un fichier @Configuration afin de pouvoir ajouter des valeurs par défaut sensibles (ex Timeouts). Pour mes tests d’intégration, je voudrais me moquer de RestTemplate car je ne souhaite pas me connecter à des services externes - je sais quelles réponses attendre. J’ai essayé de fournir une implémentation différente dans le package d’intégration-test dans l’espoir que cette dernière remplace la réelle implémentation, mais en vérifiant les journaux, c’est l’inverse: la vraie implémentation remplace celle de test. 

Comment puis-je m'assurer que celui de TestConfig est celui utilisé?

Ceci est mon fichier de configuration:

@Configuration
public class RestTemplateProvider {

    private static final int DEFAULT_SERVICE_TIMEOUT = 5_000;

    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate(buildClientConfigurationFactory());
    }

    private ClientHttpRequestFactory buildClientConfigurationFactory() {
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
        factory.setReadTimeout(DEFAULT_SERVICE_TIMEOUT);
        factory.setConnectTimeout(DEFAULT_SERVICE_TIMEOUT);
        return factory;
    }
}

Test d'intégration:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TestConfiguration.class)
@WebAppConfiguration
@ActiveProfiles("it")
public abstract class IntegrationTest {}

Classe TestConfiguration:

@Configuration
@Import({Application.class, MockRestTemplateConfiguration.class})
public class TestConfiguration {}

Et enfin MockRestTemplateConfiguration

@Configuration
public class MockRestTemplateConfiguration {

    @Bean
    public RestTemplate restTemplate() {
        return Mockito.mock(RestTemplate.class)
    }
}
19
mvlupan

Depuis Spring Boot 1.4.x, il existe une option permettant d'utiliser l'annotation @MockBean pour simuler des beans Spring.

Réaction au commentaire:

Pour conserver le contexte dans le cache, n'utilisez pas @DirtiesContext, mais utilisez @ContextConfiguration(name = "contextWithFakeBean"). Il créera un contexte séparé, tout en conservant le contexte par défaut dans le cache. Spring gardera les deux (ou le nombre de contextes que vous avez) en cache. 

Notre construction est la suivante: la plupart des tests utilisent la configuration par défaut non polie, mais nous avons 4-5 tests qui simulent des haricots. Le contexte par défaut est bien réutilisé

18
luboskrnac

1 . Vous pouvez utiliser l'annotation @Primary:

@Configuration
public class MockRestTemplateConfiguration {

    @Bean
    @Primary
    public RestTemplate restTemplate() {
        return Mockito.mock(RestTemplate.class)
    }
}

BTW, j'ai écrit blog post à propos de fake Spring bean

2 . Mais je suggérerais de jeter un œil au support de test de Spring RestTemplate . Ce serait un exemple simple: private MockRestServiceServer mockServer;

  @Autowired
  private RestTemplate restTemplate;

  @Autowired
  private UsersClient usersClient;

  @BeforeMethod
  public void init() {
    mockServer = MockRestServiceServer.createServer(restTemplate);
  }

  @Test
  public void testSingleGet() throws Exception {
    // GIVEN
    int testingIdentifier = 0;
    mockServer.expect(requestTo(USERS_URL + "/" + testingIdentifier))
      .andExpect(method(HttpMethod.GET))
      .andRespond(withSuccess(TEST_RECORD0, MediaType.APPLICATION_JSON));


    // WHEN
    User user = usersClient.getUser(testingIdentifier);

    // THEN
    mockServer.verify();
    assertEquals(user.getName(), USER0_NAME);
    assertEquals(user.getEmail(), USER0_EMAIL);
  }

Plus d'exemples peuvent être trouvés dans mon rapport Github ici

13
luboskrnac

Approfondissant un peu, voir ma deuxième réponse .

J'ai résolu le problème en utilisant 

@SpringBootTest(classes = {AppConfiguration.class, AppTestConfiguration.class})

au lieu de

@Import({ AppConfiguration.class, AppTestConfiguration.class });

Dans mon cas, le test n'est pas dans le même package que l'application. Je dois donc spécifier explicitement AppConfiguration.class (ou App.class). Si vous utilisez le même package dans le test, alors vous pouvez simplement écrire

@SpringBootTest(classes = AppTestConfiguration.class)

est pas de (ne fonctionne pas)

@Import(AppTestConfiguration.class );

C’est joli de voir que c’est tellement différent. Peut-être que quelqu'un peut expliquer cela. Je ne pouvais pas trouver de bonnes réponses jusqu'à maintenant. Vous pensez peut-être que @Import(...) n'est pas détecté si @SpringBootTests est présent, mais que le bean de remplacement apparaît dans le journal. Mais juste dans le mauvais sens.

En passant, utiliser @TestConfiguration à la place de @Configuration ne fait également aucune différence.

5
Torsten

Le problème dans votre configuration est que vous utilisez @Configuration pour votre configuration de test. Cela remplacera votre configuration principale. Utilisez plutôt @TestConfiguration qui ajoutera (remplacera) votre configuration principale.

46.3.2 Détection de la configuration de test

Si vous souhaitez personnaliser la configuration principale, vous pouvez utiliser un fichier classe @TestConfiguration imbriquée. Contrairement à une classe @Configuration imbriquée, qui serait utilisé à la place du fichier primaire de votre application. configuration, une classe imbriquée @TestConfiguration est utilisée en plus à la configuration principale de votre application.

Exemple utilisant SpringBoot:

Classe principale 

@SpringBootApplication() // Will scan for @Components and @Configs in package tree
public class Main{
}

Config principale

@Configuration
public void AppConfig() { 
    // Define any beans
}

Test config

@TestConfiguration
public void AppTestConfig(){
    // override beans for testing
} 

Classe de test

@RunWith(SpringRunner.class)
@SpringBootTest(classes = { Main.class, AppTestConfig.class })
public void AppTest() {
    // use @MockBean if you like
}

Remarque: Utiliser @Import(AppTestConfig.class) à la place ne remplacera pas un bean de AppConfig. D'une manière ou d'une autre, il sera simplement ajouté si le haricot est manquant. Appréciez tout lien vers une documentation officielle, cela le rend clair.

2
Torsten