JUnit5所有类测试完成后如何删除Docker中的Test Containers
解决JUnit5测试后Ryuk无法自动删除Testcontainers容器的方案
问题根源
- static实例脱离Spring生命周期:你代码里的
ElasticsearchTestContainer等容器实例用了static变量,这玩意儿不会跟着Spring测试上下文销毁而回收,就算DisposableBean的destroy方法执行了,static引用还在,容器资源没彻底释放,Ryuk也追踪不到这类“游离”的容器。 withReuse(true)的坑:开了容器复用模式后,Testcontainers默认不让Ryuk删容器——毕竟复用就是要留着给后续测试用,自然不会在单测结束就销毁。- Spring销毁逻辑没触发:虽然实现了
DisposableBean,但static实例可能让Spring没法正确触发销毁时机,或者注册单例的方式有问题,导致destroy根本没执行。
具体解决办法
1. 把static容器实例改成非static
把TestContainersSpringContextCustomizerFactory里的static变量全改成普通变量:
// 原来的static变量: // private static SqlTestContainer prodTestContainer; // private static S3TestContainer minioContainer; // private static ElasticsearchTestContainer elasticsearchBean; // private static RabbitMQTestContainer rabbitMQContainer; // 改成这样: private SqlTestContainer prodTestContainer; private S3TestContainer minioContainer; private ElasticsearchTestContainer elasticsearchBean; private RabbitMQTestContainer rabbitMQContainer;
这样每个测试类的Spring上下文会自己创建容器实例,上下文销毁时destroy方法会被正常调用,容器关闭后Ryuk就能识别并清理了。
2. 调整容器复用设置
如果不需要复用容器,直接删掉withReuse(true):
elasticsearchContainer = new ElasticsearchContainer( DockerImageName.parse("docker-persistent.nexus.jaga.rt.ru/elasticsearch") .asCompatibleSubstituteFor("docker.elastic.co/elasticsearch/elasticsearch") .withTag("7.17.4")) // 删掉这行 // .withReuse(true) .withStartupTimeout(Duration.of(CONTAINER_STARTUP_TIMEOUT_MINUTES, ChronoUnit.MINUTES)) .withSharedMemorySize(256000000L) .withEnv("ES_JAVA_OPTS", "-Xms256m -Xmx256m") .withEnv("xpack.security.enabled", "false") .withLogConsumer(new Slf4jLogConsumer(log));
要是确实需要复用,又想所有测试跑完后清理,就加个JVM关闭钩子:
@Override public void afterPropertiesSet() { if (null == elasticsearchContainer) { elasticsearchContainer = new ElasticsearchContainer(...) .withReuse(true) .withStartupTimeout(Duration.of(CONTAINER_STARTUP_TIMEOUT_MINUTES, ChronoUnit.MINUTES)) ...; } if (!elasticsearchContainer.isRunning()) { elasticsearchContainer.start(); // 加个钩子,JVM退出时关闭容器 Runtime.getRuntime().addShutdownHook(new Thread(() -> { if (elasticsearchContainer.isRunning()) { elasticsearchContainer.stop(); } })); } }
3. 让Spring正确管理容器销毁
在TestContainersSpringContextCustomizerFactory里注册单例时,显式注册DisposableBean,把注释掉的那行代码放开:
if (null == elasticsearchBean) { elasticsearchBean = beanFactory.createBean(ElasticsearchTestContainer.class); beanFactory.registerSingleton(ElasticsearchTestContainer.class.getName(), elasticsearchBean); // 放开这行,确保Spring调用destroy方法 ((DefaultListableBeanFactory) beanFactory).registerDisposableBean( ElasticsearchTestContainer.class.getName(), elasticsearchBean); }
4. 检查Ryuk是否正常运行
看看测试日志里有没有Ryuk started的记录,要是Ryuk没启动,排查下:
- Docker能不能正常用,有没有权限创建容器
testcontainers.properties里的ryuk.container.image配置对不对,镜像能不能拉下来- 测试进程有没有和Docker daemon通信的权限
验证步骤
- 跑测试后看Docker容器状态,确认测试结束后容器是不是退出了
- 查测试日志,看看有没有容器关闭的输出(比如
destroy里的日志) - 看Ryuk的日志,确认它收到了清理容器的指令
内容的提问来源于stack exchange,提问作者DimaMsuVmk22
相关产品推荐
相关产品推荐

