Переопределение beans в тестах интеграции

Для моего приложения Spring -Boot я предоставляю RestTemplate, хотя файл @Configuration, поэтому я могу добавить разумные значения по умолчанию (ex Timeouts). Для моих интеграционных тестов я хотел бы высмеять RestTemplate, поскольку я не хочу подключаться к внешним сервисам - я знаю, какие ответы ожидать. Я попытался предоставить другую реализацию в пакете интеграции-теста в надежде, что последний переопределит реальную реализацию, но проверка журналов выглядит наоборот: реальная реализация переопределяет тестовую.

Как я могу убедиться, что тот из TestConfig используется?

Это мой конфигурационный файл:

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

Тест интеграции:

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

Класс TestConfiguration:

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

И, наконец, MockRestTemplateConfiguration

@Configuration
public class MockRestTemplateConfiguration {

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

Ответ 1

Так как Spring Boot 1.4.x существует возможность использовать аннотацию @MockBean для подделки Spring beans.

Реакция на комментарий:

Чтобы сохранить контекст в кеше, не используйте @DirtiesContext, но используйте @ContextConfiguration(name = "contextWithFakeBean"), и он создаст отдельный контекст, в то время как он будет поддерживать контекст по умолчанию в кеше. Spring сохранит оба (или сколько у вас контекстов) в кеше.

Наша сборка осуществляется таким образом, когда большинство тестов используют неподлинную конфигурацию по умолчанию, но у нас есть 4-5 тестов, которые притворяются beans. По умолчанию контекст красиво используется повторно

Ответ 2

1. Вы можете использовать аннотацию @Primary:

@Configuration
public class MockRestTemplateConfiguration {

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

Кстати, я написал сообщение в блоге о подделке Spring bean

2. Но я бы предложил посмотреть Spring Поддержка тестирования RestTemplate. Это будет простой пример: частный 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);
  }

Дополнительные примеры можно найти в моем репозитории Github здесь

Ответ 3

Пройдя немного глубже, посмотрите мой второй ответ.

Я решил проблему с помощью

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

вместо

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

В моем случае тест находится не в том же пакете, что и приложение. Поэтому мне нужно явно указать AppConfiguration.class (или App.class). Если вы используете один и тот же пакет в тесте, чем, я думаю, вы могли бы просто написать

@SpringBootTest(classes = AppTestConfiguration.class)

вместо (не работает)

@Import(AppTestConfiguration.class );

Это довольно сложно увидеть, что это так по-другому. Может быть, кто-то может объяснить это. Я не мог найти хорошие ответы до сих пор. Вы можете подумать, что @Import(...) не @SpringBootTests если присутствует @SpringBootTests, но в журнале отображается переопределенный компонент. Но как раз наоборот.

Кстати, использование @TestConfiguration вместо @Configuration также не имеет значения.

Ответ 4

Проблема в вашей конфигурации заключается в том, что вы используете @Configuration для своей тестовой конфигурации. Это заменит вашу основную конфигурацию. Вместо этого используйте @TestConfiguration которая добавит (переопределит) вашу основную конфигурацию.

46.3.2 Определение конфигурации теста

Если вы хотите настроить первичную конфигурацию, вы можете использовать вложенный класс @TestConfiguration. В отличие от вложенного класса @Configuration, который будет использоваться вместо первичной конфигурации ваших приложений, в дополнение к первичной конфигурации ваших приложений используется вложенный класс @TestConfiguration.

Пример использования SpringBoot:

Основной класс

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

Основной конфиг

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

Тестовый конфиг

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

Тестовый класс

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

Примечание: знайте, что будут созданы все Бины, даже те, которые вы переопределяете. Используйте @Profile если вы не хотите создавать экземпляр @Configuration.

Ответ 5

Проверьте этот ответ вместе с другими, предоставленными в этой теме. Это о переопределении bean-компонента в Spring Boot 2.X, где эта опция была отключена по умолчанию. В нем также есть некоторые идеи о том, как использовать Bean Definition DSL, если вы решили пойти по этому пути.