-1

I have a Spring application that I am trying to test with EmbededRedis. So I created a component like below to Initialize and kill redis after test.

@Component
public class EmbededRedis {

  @Value("${spring.redis.port}")
  private int redisPort;

  private RedisServer redisServer;

  @PostConstruct
  public void startRedis() throws IOException {
    redisServer = new RedisServer(redisPort);
    redisServer.start();
  }

  @PreDestroy
  public void stopRedis() {
    redisServer.stop();
  }
}

But now I am facing a weird issue. Because spring caches the context, PreDestroy doesnt get called everytime after my test is executed, but for some reason, @PostConstruct gets called, and EmbededRedis tries to start the running redis server again and again, which is creatimg issues in the execution.

Is there a way to handle this situation by any mean?

Update This is how I am primarily defining my tests.

@SpringBootTest(classes = {SpringApplication.class})
@ActiveProfiles("test")
public class RedisApplicationTest {
Anand Vaidya
  • 1,374
  • 11
  • 26
  • If there is a test with another active profile or an `@TestProopertySource` or a different `classes` part it will load a new context. See https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/testing.html#testcontext-ctx-management-caching on what is used for caching, if your test has something that is different a new context will be loaded instead of reusing an existing one. – M. Deinum Aug 19 '20 at 11:53

2 Answers2

2

Ditch the class and write an @Configuration class which exposed RedisServer as a bean.

@Configuration
public void EmbeddedRedisConfiguration {

   @Bean(initMethod="start", destroyMethod="stop")
   public RedisServer embeddedRedisServer(@Value("${spring.redis.port}") int port) {
      return new RedisServer(port);
   }
}
M. Deinum
  • 115,695
  • 22
  • 220
  • 224
  • My problem is similar to this - https://stackoverflow.com/questions/22773116/spring-why-is-predestroy-not-called-at-end-of-each-test-class Spring test is preserving the context for some reason.. do you think this will work in such scenario? doesnt harm in trying though, – Anand Vaidya Aug 19 '20 at 11:38
  • For performance reasons the ocntext is cached and only destroyed when the jvm is destroyed (or the cache runs out-of-entries). If you want to reload everything you would need to annotate your test with `@DirtiesContext` **however** that has a severe impact on your test performance as it will reload everything. Now do you really need that or would it be better to write your test in a different way NOT to rely on empty state. Also if this embedded redis is for test, you might want to use another solution instead of making it part of the application. – M. Deinum Aug 19 '20 at 11:40
  • Actually I dont want to dirty the context, but I am wondering why PostConstruct gets called while PreDestroy doesnt get called.. if its supposed to resuse the context, why does it reinitialize is the question. – Anand Vaidya Aug 19 '20 at 11:42
  • Without seeing your tests that is impossible to answer. The `@PostConstruct` is only called when a context is created and that happens if the configuration in your test is different (and thus it isn't cached but re-created). The context is cached based on app-context files (classes, XML), test execution listeners, properties, etc. so if one of those is different it will create a new application context and thus `@PostConstruct` is called. Which is what I suspect is happening. – M. Deinum Aug 19 '20 at 11:45
  • That being said, if you can use something like docker, I strongly suggest something like [TestContainers](https://www.testcontainers.org/) instead of this solution. – M. Deinum Aug 19 '20 at 11:47
  • updated the question. actually we are not using containers etc.. its plain `@SpringBoottest` that we are using. – Anand Vaidya Aug 19 '20 at 11:49
  • I'm nowhere stating you should use containers, but if you can for testing I strongly suggest testcontainers. But as stated the `@PostConstruct` is only called when it is created, the context is created if one of the things making up the [cache key](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/testing.html#testcontext-ctx-management-caching) is different. I suspect (without seeing all your `@SpringBootTest` and others) that that is what is happening. – M. Deinum Aug 19 '20 at 11:52
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/220089/discussion-between-anand-vaidya-and-m-deinum). – Anand Vaidya Aug 19 '20 at 12:11
  • Probably what happens is that you are seeing all @PostConstruct calls when debugging, for the initialization of each context, and they are all closed at the end of your whole suite. PostConstruct is called for each encountered context, so it looks like it is the only method called, but this is because it is caching every context instance. At the end of the build, it just destroys them all at once – Whimusical Dec 15 '20 at 17:47
0

So I debuged the ContextInitialization as suggested by @M. Deinum.

For me, the porblem was, Our application was mocking different classes in order to mix mocking with Spring context. Now, when you use mocks, MockitoContextInitializer also becomes part of your cache key, which results in cache miss. Reason is, The classes under mock are obviously different for different test classes.

Looking at the situation, I preferred to go ahead with @DirtiesContext to invalidate the contest after the test is done, so that I can reinitialize the context later on for different test.

Note @DirtiesContext is in a way recommended to be avoided as it slows down your tests.

Anand Vaidya
  • 1,374
  • 11
  • 26