Testing Spring's @Async void-returning methods

4.8k views Asked by At

I have a little problem with @Async methods that return void (or Unit, I'm writing in Kotlin) in my Spring app.

I don't know why, but when @Async method returns void it just doesn't execute, or at least it doesn't do what it is supposed to. Need to say, that in my async methods, I want to send an email using the JavaMailSender, so no big deal. Here is the method:

@Async
override fun sendEmail(locale: Locale, subject: String, message: String, to: String) {

    val msg = sender.createMimeMessage()
    println("1")
    val helper = MimeMessageHelper(msg, true, "utf-8")
    helper.setFrom("[email protected]")
    helper.setTo(to)
    println("2")
    helper.setSubject(getSubject(locale, null))
    println("3")
    helper.setText(processTemplate(locale, null, null), true)
    println("4")
    sender.send(msg)
    println("5")
}

But an email never comes, there is no exception logged (I am running a testNG test).

When I change the signature of the function to make it return Future<String> and add some dummy return line to the end of the function and then I call service.sendEmail(...).get(), the method's body magically executes and the email arrives.

In my @Configuration class, there is @EnableAsync. I also implemented AsyncConfigurer and provided own executor and exception handler, because I thought it could have been something with my executor bean definition, but nothing helps.

This is driving me crazy, because I just want to silently execute something in the background and it doesn't work. By silently I mean that I don't want to be bothered by exceptions thrown inside.

Do you have any ideas?

Update: So as @pleft advised, I put some prints inside my method. Now, when I run mvn clean test, I can see that 1,2,3 are printed, but not each time. Sometimes just 1 and 2 got printed. I also put print in my AsyncUncaughtExceptionHandler, but that one is not called. It looks like the background thread is being killed too soon or something.

Update 2:

My ServiceConfig:

@Configuration
@EnableScheduling
@EnableAsync
@ComponentScan
class ServiceConfig : ApplicationContextAware, EnvironmentAware, AsyncConfigurer {
    override fun getAsyncUncaughtExceptionHandler(): AsyncUncaughtExceptionHandler {
        return AsyncUncaughtExceptionHandler { ex, method, params ->
            println("Exception thrown")
        }
    }

    override fun getAsyncExecutor(): Executor {
        val executor = ThreadPoolTaskExecutor()
        executor.corePoolSize = 2
        executor.maxPoolSize = 2
        executor.setQueueCapacity(500)
        executor.threadNamePrefix = "asyncThread"
        executor.initialize()
        return executor
    }
}
/// other beans definitions..

Maybe it is important, maybe not, but I'm using Thymeleaf in that processTemplate method.

1

There are 1 answers

0
Maroš Šeleng On BEST ANSWER

Based on the conversation in the comment and my own observation (like putting some sleep in the test method) I found out the reason. This was cause by the destroying of the Spring Context inside the test. Because I had only one test running this async method, so the scenario was that the async method returned immediately and so did the test method. As it was the last (and only) method in my test suite, the testing Context was destroyed (and also all threads it created).

So the resolution is to either put sleeps in the tests (very ugly) or have a look at this question and get inspired.