wake-up-neo.com

Beans in Integrationstests überschreiben

Für meine Spring-Boot-App stelle ich ein RestTemplate über eine @Configuration-Datei bereit, damit ich vernünftige Standardwerte (ex Timeouts) hinzufügen kann. Für meine Integrationstests möchte ich das RestTemplate verspotten, da ich mich nicht mit externen Diensten verbinden möchte - ich weiß, welche Antworten erwarten. Ich habe versucht, eine andere Implementierung im Integrations-Testpaket bereitzustellen, in der Hoffnung, dass letztere die tatsächliche Implementierung überschreibt. Die Überprüfung der Protokolle ist jedoch umgekehrt: Die tatsächliche Implementierung überschreibt die Testimplementierung. 

Wie kann ich sicherstellen, dass es sich bei der TestConfig um die verwendete handelt?

Dies ist meine Konfigurationsdatei:

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

Integrationstest:

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

TestConfiguration-Klasse:

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

Und schließlich MockRestTemplateConfiguration

@Configuration
public class MockRestTemplateConfiguration {

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

Seit Spring Boot 1.4.x gibt es eine Option zur Verwendung von @MockBean Annotation, um Spring Beans zu fälschen.

Reaktion auf Kommentar:

Um den Kontext im Cache zu behalten, verwenden Sie nicht @DirtiesContext, sondern @ContextConfiguration(name = "contextWithFakeBean"). Dadurch wird ein separater Kontext erstellt, während der Standardkontext im Cache bleibt. Spring speichert beide (oder wie viele Kontexte Sie haben) im Cache. 

Unser Build ist auf diese Weise, bei der die meisten Tests Standardkonfigurationen verwenden, die nicht verunreinigt sind, aber es gibt 4-5 Tests, die Beans fälschen. Der Standardkontext wird gut wiederverwendet

18
luboskrnac

1 . Sie können die @Primary-Anmerkung verwenden:

@Configuration
public class MockRestTemplateConfiguration {

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

Übrigens, ich schrieb Blogpost über das Fälschen von Spring Bean

2 . Aber ich würde vorschlagen, einen Blick auf Spring RestTemplate - Testunterstützung zu werfen. Dies wäre ein einfaches Beispiel: privater 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);
  }

Weitere Beispiele finden Sie in mein Github-Repo hier

13
luboskrnac

Ein bisschen tiefer in sie hineingehen, siehe meine zweite Antwort .

Ich habe das Problem mit gelöst 

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

anstatt

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

In meinem Fall ist der Test nicht im selben Paket wie die App. Deshalb muss ich die AppConfiguration.class (oder die App.class) explizit angeben. Wenn Sie dasselbe Paket im Test verwenden, könnten Sie einfach schreiben

@SpringBootTest(classes = AppTestConfiguration.class)

isntead (funktioniert nicht)

@Import(AppTestConfiguration.class );

Es ist ziemlich verdrahtet zu sehen, dass dies so anders ist. Vielleicht kann jemand das erklären. Ich konnte bis jetzt keine guten Antworten finden. Sie denken vielleicht, @Import(...) wird nicht abgeholt, wenn @SpringBootTests vorhanden ist, aber im Protokoll wird die übergeordnete Bean angezeigt. Aber nur der falsche Weg.

Übrigens macht auch die Verwendung von @TestConfiguration@Configuration keinen Unterschied.

5
Torsten

Das Problem in Ihrer Konfiguration besteht darin, dass Sie @Configuration für Ihre Testkonfiguration verwenden. Dadurch wird Ihre Hauptkonfiguration ersetzt. Verwenden Sie stattdessen @TestConfiguration, wodurch Ihre Hauptkonfiguration angehängt wird.

46.3.2 Testkonfiguration erkennen

Wenn Sie die primäre Konfiguration anpassen möchten, können Sie eine .__ verwenden. geschachtelte @ TestConfiguration-Klasse. Im Gegensatz zu einer verschachtelten @Configuration-Klasse wird die anstelle der primären Anwendung Ihrer Anwendung verwendet würde Konfiguration wird zusätzlich eine geschachtelte @ TestConfiguration-Klasse verwendet. der primären Konfiguration Ihrer Anwendung.

Beispiel mit SpringBoot:

Hauptklasse 

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

Main config

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

Test config

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

Testklasse

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

Hinweis: Wenn Sie stattdessen @Import(AppTestConfig.class) verwenden, wird ein Bean nicht von AppConfig überschrieben. Irgendwie wird es nur hinzugefügt, wenn die Bean fehlt. Schätze jeden Link zu einer offiziellen Dokumentation, das macht das klar.

2
Torsten

Check this antworte zusammen mit anderen in diesem Thread. Es geht darum, Bean in Spring Boot 2.X zu überschreiben, wo diese Option standardmäßig deaktiviert war. Es enthält auch einige Ideen zur Verwendung von Bean Definition DSL, wenn Sie sich für diesen Weg entschieden haben.

0
yuranos87

@MockBean Und Bean Overriding, die vom OP verwendet werden, sind zwei komplementäre Ansätze.

Sie möchten @MockBean Verwenden, um ein Mock zu erstellen und die eigentliche Implementierung zu vergessen: Im Allgemeinen tun Sie dies für Slice-Tests oder Integrationstests, bei denen einige Beans nicht geladen werden, von denen die zu testenden Klassen abhängen, und - dass Sie diese Beans nicht in der Integration testen möchten.
Spring macht sie standardmäßig zu null, Sie werden das minimale Verhalten verspotten, mit dem sie Ihren Test erfüllen.

@WebMvcTest Erfordert sehr oft diese Strategie, da Sie nicht alle Ebenen testen möchten, und @SpringBootTest Erfordert dies möglicherweise auch, wenn Sie in der Testkonfiguration nur eine Teilmenge Ihrer Beans-Konfiguration angeben.

Andererseits möchten Sie manchmal einen Integrationstest mit so vielen realen Komponenten wie möglich durchführen, also nicht @MockBean Verwenden, sondern ein Verhalten, eine Abhängigkeit leicht überschreiben oder ein neues definieren In diesem Fall ist der folgende Ansatz das Überschreiben von Beans:

@SpringBootTest({"spring.main.allow-bean-definition-overriding=true"})
@Import(FooTest.OverrideBean.class)
public class FooTest{    

    @Test
    public void getFoo() throws Exception {
        // ...     
    }

    @TestConfiguration
    public static class OverrideBean {    

        // change the bean scope to SINGLETON
        @Bean
        @Scope(ConfigurableBeanFactory.SINGLETON)
        public Bar bar() {
             return new Bar();
        }

        // use a stub for a bean 
        @Bean
        public FooBar BarFoo() {
             return new BarFooStub();
        }

        // use a stub for the dependency of a bean 
        @Bean
        public FooBar fooBar() {
             return new FooBar(new StubDependency());
        }

    }
}
0
davidxxx