22

I would like to disable @EnableAsync when I run my integration tests.

I tried to override the configuration file which is annotated with @EnableAsync with a class with the same name in my test package but it does not work.

In this topic: Is it possible to disable Spring's @Async during integration test?

I have seen that:

You can... Create a test configuration or simply override the task executor with a SyncTaskExecutor

but I do not understand how to do.

Any advice ? Thanks

fap
  • 663
  • 1
  • 5
  • 14
psv
  • 3,147
  • 5
  • 32
  • 67

5 Answers5

36

The topic you linked does offer a good solution.

To create a SyncTaskExecutor for tests, make sure you actually have a test configuration class for spring context. Please, refer to Spring docs for that: https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html

In this configuration class add a new bean:

@Bean
@Primary
public TaskExecutor taskExecutor() {
    return new SyncTaskExecutor();
}

That should do it!

Take care not to create this bean in your live config!

Ilya Novoseltsev
  • 1,803
  • 13
  • 19
  • 9
    What I finally done is add a `@Profile("!test")` on my AsyncConfiguration class. Thanks – psv Mar 15 '18 at 15:30
  • This will only override the async task executor bean definition IF they have the same bean name `taskExecutor`. `@Primary` is not used here. `@Primary` does not work when overriding bean definitions, but is meant for when there are multiple beans with same qualifiers. Also, overriding will only work if this bean definition is processed by Spring after the main bean, which is often the case for inner `@TestConfiguration` class. – qtips Dec 27 '22 at 15:28
  • Made my day! I have got a `@SpringBootTest` with `@Transactional` and some `@Async` stuff happening deep in the background somewhere, which wasn’t aware of the db setup stuff in my `@BeforeEach` method, because of thread-bound transactions. D‘oh! – andy Mar 07 '23 at 06:18
7

If a unique profile name is used when the tests are run (e.g. "test"), then the easiest way to disable async when running tests is with

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.scheduling.annotation.EnableAsync;


@Profile("!test")
@Configuration
@EnableAsync
public class AsyncConfiguration {

}

In my case I had to add the following to src/test/resources/application.yml to ensure tests are run under a profile named "test"

spring:
  profiles:
    active: test
Dónal
  • 185,044
  • 174
  • 569
  • 824
1

You can overwrite the main task executor by creating the following class in test folder:

@TestConfiguration
public class TestAsyncConfig {
    // create this bean if you have a custom executor you want to overwrite
    @Bean(name = "xxxxxxx") 
    public Executor xxxxxxx() {
        return new SyncTaskExecutor();
    }

    // this will overwrite the default executor
    @Bean
    public Executor taskExecutor() { 
        return new SyncTaskExecutor();
    }
}

Then add the following to annotations on your integration test:

@ContextConfiguration(classes = TestAsyncConfig.class)
Oleg
  • 187
  • 3
  • 5
-1

We ended up using a prop yourcompany.someExecutor.async that we default to true (so it does not show up in the application.yml) and in a test we set it to false using TestPropertySource. Based on that prop we either initialize a SyncTaskExecutor or some async version (e.g. ThreadPoolTaskExecutor).

Note that this also enables to use multiple props so it is easy to disable a specific executor per prop. In our case we have several async executers depending on the context.

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@TestPropertySource(properties = {
        "yourcompany.someExecutor.async=false",
})
public class SomeIntegrationTest {
  // ... tests requiring async disabled
}
@Configuration
public class SomeConfig {
    // ...
    @Value("${yourcompany.someExecutor.async:true}")
    private boolean asyncEnabled;

    @Bean("someExecutor") // specific executor
    public Executor algoExecutor() {
        if (!asyncEnabled) {
            return new SyncTaskExecutor();
        }
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(THREAD_COUNT);
        executor.setMaxPoolSize(THREAD_COUNT);
        executor.setQueueCapacity(QUEUE_CAPACITY);
        executor.setThreadNamePrefix("Some-");
        executor.initialize();
        return executor;
    }
}
Stuck
  • 11,225
  • 11
  • 59
  • 104
-2

You can also create two methods in your class, one that has the @Async annotation in it, and a second one that will have all the logic that you need to test without this annotation and make the first method call the second. Then in your tests you call the second method that will have package-private visibility.

Ex:

@Async
public void methodAsync() {
    this.method();
}

void method() {
   // Your business logic here!
}
  • 1
    You should not change production code just for tests – Tyulpan Tyulpan Jul 22 '21 at 18:06
  • You are just by passing the framework tooling here in my point of view. One could also instead of returning void, return a `CompletableFuture` and during tests force a `CompletableFuture.completedFuture()` with a proper value. In the end you need to make sure your business logic works, not the framework you are using. – Felipe Moraes Dec 09 '21 at 14:23