Java JUnit测试Spring@Async void服务方法

Java JUnit测试Spring@Async void服务方法,java,spring,junit,spring-async,Java,Spring,Junit,Spring Async,我有春季服务: @Service @Transactional public class SomeService { @Async public void asyncMethod(Foo foo) { // processing takes significant time } } 我对这个SomeService进行了集成测试: @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConf

我有春季服务:

@Service
@Transactional
public class SomeService {

    @Async
    public void asyncMethod(Foo foo) {
        // processing takes significant time
    }
}
我对这个
SomeService
进行了集成测试:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
@IntegrationTest
@Transactional
public class SomeServiceIntTest {

    @Inject
    private SomeService someService;

        @Test
        public void testAsyncMethod() {

            Foo testData = prepareTestData();

            someService.asyncMethod(testData);

            verifyResults();
        }

        // verifyResult() with assertions, etc.
}
问题是:

  • assomeservice.asyncMethod(..)用
    @Async
  • 因为
    SpringJUnit4ClassRunner
    遵循
    @Async
    语义
testAsyncMethod
线程将调用
someService.asyncMethod(testData)
分叉到它自己的工作线程中,然后直接继续执行
verifyResults()


在验证结果之前,如何等待
someService.asyncMethod(testData)
的完成?请注意,此处不适用的解决方案,因为
someService.asyncMethod(testData)
返回
void
,而不是
未来的

,以便遵守
@Async
语义,例如

为了解决我的问题,我引入了一个新的Spring配置文件
非异步

如果
非异步
配置文件处于活动状态,则使用
异步配置

@Configuration
@EnableAsync
@EnableScheduling
@Profile("!non-async")
public class AsyncConfiguration implements AsyncConfigurer {

  // this configuration will be active as long as profile "non-async" is not (!) active

}
@Configuration
// notice the missing @EnableAsync annotation
@EnableScheduling
@Profile("non-async")
public class NonAsyncConfiguration {

  // this configuration will be active as long as profile "non-async" is active

}
如果非异步配置文件处于活动状态,则使用
非同步配置

@Configuration
@EnableAsync
@EnableScheduling
@Profile("!non-async")
public class AsyncConfiguration implements AsyncConfigurer {

  // this configuration will be active as long as profile "non-async" is not (!) active

}
@Configuration
// notice the missing @EnableAsync annotation
@EnableScheduling
@Profile("non-async")
public class NonAsyncConfiguration {

  // this configuration will be active as long as profile "non-async" is active

}
现在在有问题的JUnit测试类中,我显式激活了“非异步”配置文件,以便相互排除异步行为:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
@IntegrationTest
@Transactional
@ActiveProfiles(profiles = "non-async")
public class SomeServiceIntTest {

    @Inject
    private SomeService someService;

        @Test
        public void testAsyncMethod() {

            Foo testData = prepareTestData();

            someService.asyncMethod(testData);

            verifyResults();
        }

        // verifyResult() with assertions, etc.
}
如果您使用的是Mockito(直接或通过Spring测试支持
@MockBean
),它有一个验证模式,在这种情况下有一个超时:

你也可以使用等待(在互联网上找到的,还没有尝试过)。


如果您的方法返回
CompletableFuture
则使用
join
method-

此方法等待异步方法完成并返回结果。遇到的任何异常都将在主线程中重新调用。

我已通过注入完成 ThreadPoolTaskExecutor

然后

executor.getThreadPoolExecutor().awaitTermination(1,TimeUnit.SECONDS)

在验证结果之前, 报告内容如下:

  @Autowired
  private ThreadPoolTaskExecutor executor;

    @Test
    public void testAsyncMethod() {

        Foo testData = prepareTestData();

        someService.asyncMethod(testData);

        executor.getThreadPoolExecutor().awaitTermination(1, TimeUnit.SECONDS);

        verifyResults();
    }

为了扩展@bastiat的答案(我认为这应该是正确的答案),如果您与多个执行者一起工作,您还应该指定
TaskExecutor
。因此,您需要注入您希望等待的正确的。那么,让我们假设我们有以下配置

@Configuration
@EnableAsync
public class AsyncConfiguration {

    @Bean("myTaskExecutor")
    public TaskExecutor myTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setMaxPoolSize(15);
        executor.setCoreCapacity(10);
        executor.setQueueCapacity(Integer.MAX_VALUE);
        executor.setThreadNamePrefix("MyTaskExecutor-");
        executor.initialize();
        return executor;
    }

    // Everything else

}
然后,您将得到一个类似于下面的服务

@Service
public class SomeServiceImplementation {

    @Async("myTaskExecutor")
    public void asyncMethod() {
         // Do something
    }

    // Everything else

}
@Autowired
private SomeService someService;

@Autowired
private ThreadPoolTaskExecutor myTaskExecutor;

@Test
public void testAsyncMethod() {

    Foo testData = prepareTestData();

    this.someService.asyncMethod(testData);

    this.myTaskExecutor.getThreadPoolExecutor().awaitTermination(1, TimeUnit.SECONDS);

    this.verifyResults();

    // Everything else
}
现在,扩展@bastiat answer,测试将如下所示

@Service
public class SomeServiceImplementation {

    @Async("myTaskExecutor")
    public void asyncMethod() {
         // Do something
    }

    // Everything else

}
@Autowired
private SomeService someService;

@Autowired
private ThreadPoolTaskExecutor myTaskExecutor;

@Test
public void testAsyncMethod() {

    Foo testData = prepareTestData();

    this.someService.asyncMethod(testData);

    this.myTaskExecutor.getThreadPoolExecutor().awaitTermination(1, TimeUnit.SECONDS);

    this.verifyResults();

    // Everything else
}

此外,我还有一个与问题无关的小建议。我不会将
@Transactional
注释添加到服务,只会添加到DAO/存储库。除非您需要将它添加到一个特定的服务方法中,该方法必须是原子的

有趣的方法,但这仅在没有spring配置依赖于任务执行器的情况下才有效。如果异步测试是一个问题,我建议将它们转移到一个单独的surefire Execution。您上面提到的好的解决方案是一个可行的解决方案,将asyncMethod结果类型公开为Future,并返回新的AsyncResult(null);在异步方法的最后;在测试方法中,从asyncMethod获取未来并等待它,如
future f=someService.asyncMethod(testData);f、 get();验证结果()或者,您可以使用CompletableFuture::Get如果在同一spring上下文中有多个测试,那么您必须注意重新启动执行器,对吗?