英文:
Multiple Application Contexts in Spring JUnit test with WireMock
问题
我有以下情景:
@Transactional
@SpringBootTest
@ActiveProfiles("test")
@AutoConfigureMockMvc
@AutoConfigureWireMock(port = 0)
public abstract class IntegrationTest {
}
public class Test1 extends IntegrationTest {
// 使用 WireMock 的测试
}
@ActiveProfiles("specific-case-test") // 这会导致创建另一个应用程序上下文。
public class Test2 extends IntegrationTest {
// 使用 WireMock 的测试
}
public class Test3 extends IntegrationTest {
// 使用 WireMock 的测试
}
在所有这些情况下,测试都能成功运行:
- 单独运行每个测试
- 按顺序:Test1,Test3,Test2
- 按顺序:Test3,Test1,Test2
- 按顺序:Test2,Test3,Test1
- 按顺序:Test2,Test1,Test3
在所有这些情况下,最后一个运行的测试都会失败:
- 按顺序:Test1,Test2,Test3
- 按顺序:Test3,Test2,Test1
我已经调查了问题,与 Spring 应用程序上下文和 WireMock 有关。
发生了什么?我们考虑测试按照顺序运行:Test1,Test2,Test3
。
当运行 Test1
时,会创建一个应用程序上下文(AC1),并且会设置一个 WireMock 服务器(WM1),假设在端口 1 上。端口 1 被设置为 AC1(wiremock.server.port
),并且 WM1 会附加到测试线程。所有测试都通过。
当运行 Test2
时,会创建另一个应用程序上下文(AC2),并且会设置一个新的 WireMock 服务器(WM2),假设在端口 2 上。端口 2 被设置为 AC2(wiremock.server.port
),并且 WM2 会替换 WM1 附加到测试线程。所有测试都通过。
当运行 Test3
时,它重用了 AC1,这导致测试失败,错误消息为:404 Not Found: [No response could be served as there are no stub mappings in this WireMock instance.]
。
应用程序的状态是 wiremock.server.port
为 1(来自 AC1),并且 WM2 附加到测试线程。因此,存根匹配会针对 WM2 进行,但应用程序的 REST 调用会发送到监听在端口 1 上的 WM1。
我已经尝试通过在 Test2 中添加 @DirtiesContext
来清理应用程序上下文,以便强制 Spring 加载第三个 AC,但不起作用。但是,如果我在 Test1 中添加 @DirtiesContext
,或者在 Test3 中添加 @DirtiesContext(classMode = BEFORE_CLASS)
,它会起作用。我不希望使用这种解决方案,因为我还有其他测试,并且无法保证测试的运行顺序,所以如果我在 Test3 中添加它,然后执行顺序会更改,另一个测试将失败。我希望有一个真正的解决方案。
有什么想法吗?
英文:
I have the following scenario:
@Transactional
@SpringBootTest
@ActiveProfiles("test")
@AutoConfigureMockMvc
@AutoConfigureWireMock(port = 0)
public abstract class IntegrationTest {
}
public class Test1 extends IntegrationTest {
// Tests that use WireMock
}
@ActiveProfiles("specific-case-test") // This causes another Application Context to be created.
public class Test2 extends IntegrationTest {
// Tests that use WireMock
}
public class Test3 extends IntegrationTest {
// Tests that use WireMock
}
Tests run successfully in all these scenarios:
- Run the tests individually
- In the order: Test1, Test3, Test2
- In the order: Test3, Test1, Test2
- In the order: Test2, Test3, Test1
- In the order: Test2, Test1, Test3
The last test to run fails in all these scenarios:
- In the order: Test1, Test2, Test3
- In the order: Test3, Test2, Test1
I already investigated the problem and it is related to the Spring Application Context and WireMock.
What is going on? Let's consider that the tests run in that order: Test1, Test2, Test3
.
When Test1
runs, an Application Context (AC1) is created and a WireMock server (WM1) is set up, let's say, on port 1. Port 1 is set to AC1 (wiremock.server.port
) and WM1 is attached to the test thread. All test passes.
When Test2
runs, another Application Context (AC2) is created and a new WireMock server (WM2) is set up, let's say, on port 2. Port 2 is set to AC2 (wiremock.server.port
) and WM2 is attached to the test thread, replacing WM1. All test passes.
When Test3
runs, it reuses AC1 and this causes the tests to fail with the message: 404 Not Found: [No response could be served as there are no stub mappings in this WireMock instance.]
.
The application state is that wiremock.server.port
is 1 (comes from AC1) and WM2 is attached to the test thread. Because of that, stubbing happens against WM2, but the application rest calls are going to WM1, which is listening on port 1.
I already tried to clean the Application Context adding @DirtiesContext
to Test2, so it would force Spring to load the third AC, but it doesn't work. However, if I add @DirtiesContext
to Test1 or @DirtiesContext(classMode = BEFORE_CLASS)
to Test3 it works. I don't want this solution because I have other tests and there is no guarantee in which order the tests will run, so if I add it to Test3, then later the execution order will change and another test will fail. I would like a real solution.
Any ideas?
答案1
得分: 3
不确定您是否已经找到解决方法,但我是如何解决这个问题的。
当Spring的缓存测试上下文被重用时,WireMock端口会更改回该上下文的端口,但似乎会错过配置“WireMock”类的默认服务器配置的某些步骤。我们必须在@Before
或@BeforeEach
方法中调用WireMock.configureFor(port)
,并使用当前运行上下文的端口。这意味着当我们进行stubFor
方法调用时,会命中正确的WireMock端口,并且服务器会正确地配置我们的存根。确保在进行任何重置之前进行此配置。
@Autowired
private Environment environment;
private String getWiremockServerPort() {
// 从当前Spring上下文的环境中获取自动配置的端口属性
return environment.getProperty("wiremock.server.port");
}
@BeforeEach
private void configureWireMockPortToMatchEnvironmentContext() {
int contextEnvironmentPort = Integer.parseInt(getWiremockServerPort());
configureFor(contextEnvironmentPort);
}
英文:
Not sure if you have found a solution to this but here is how I resolved this.
When the spring cached test contexts are reused the wiremock port changes back to that contexts port but seems to miss some step of configuring the WireMock
classes default server config. We had to call WireMock.configureFor(port)
in a @Before
or @BeforeEach
method with the port of the currently running context. This meant that when we did stubFor
method calls then the right WireMock port was hit and the server got configured with our stubs correctly, make sure you do this configuring before any resets too.
@Autowired
private Environment environment;
private String getWiremockServerPort() {
// Get the auto configured port property from the current Spring contexts environment
return environment.getProperty("wiremock.server.port");
}
@BeforeEach
private void configureWireMockPortToMatchEnvironmentContext() {
int contextEnvironmentPort = Integer.parseInt(getWiremockServerPort());
configureFor(contextEnvironmentPort);
}
答案2
得分: 3
如果您是以这种方式创建 Wiremock 存根:
stubFor(post(urlEqualTo(CALLBACK))
.willReturn(aResponse().withStatus(202))
);
其中使用了来自 com.github.tomakehurst.wiremock.client.Wiremock 的静态方法 "stubFor",您可以尝试在测试类中添加一个自动装配的 WiremockServer 对象:
@Autowired
protected WireMockServer wireMockServer;
然后将您的存根创建代码替换为:
wireMockServer.stubFor(post(urlEqualTo(CALLBACK))
.willReturn(aResponse().withStatus(202))
);
其中使用了来自 com.github.tomakehurst.wiremock.WireMockServer 的非静态方法。
在我的情况下,这解决了问题。
英文:
If you are creating Wiremock stubs in this way:
stubFor(post(urlEqualTo(CALLBACK))
.willReturn(aResponse().withStatus(202))
);
where used static method "stubFor" from com.github.tomakehurst.wiremock.client.Wiremock, you may try to add in your test class an autowired WiremockServer object:
@Autowired
protected WireMockServer wireMockServer;
and then replace your stub creation with:
wireMockServer.stubFor(post(urlEqualTo(CALLBACK))
.willReturn(aResponse().withStatus(202))
);
where used non-static method from com.github.tomakehurst.wiremock.WireMockServer.
In my case, this solved the problem.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论