citrusframework / citrusframework/citrus
async actions fail without behavior wrapper
- Dominant language
- Java
- Stars
- 485
- Forks
- 155
- Avg merge
- 4d 22h
- Merged PRs (30d)
- 6
Description
**Root cause analysis outstanding**
**Citrus Version**
2.7.8
**Expected behavior**
When I use a async container, it doesn't matter if I wrap a single `TestAction` in a `AbstractTestBehavior` or not.
**Actual behavior**
It matters, because the test fails, if I don't wrap my single jms `TestAction`.
**Test case sample**
Unfortunately the test case is not that simple.
Passing test case - with behavior wrapper:
```java
public class TodoListIT extends TestNGCitrusTestRunner {
@Autowired
private HttpClient httpClient;
@Autowired
private HttpServer httpServer;
@Autowired
private JmsEndpoint jms;
private String payload = "{\"foo\": 123}";
@Test
@CitrusTest
public void testGet() {
async().actions(
simulateThirdPartyRestApi(),
verifyForwardToInternalJms());
simulateSUT();
}
private void simulateSUT() {
//Request a payload from a third party REST API
http(http -> http
.client(httpClient)
.send()
.post("/bar"));
http(http -> http
.client(httpClient)
.receive()
.response()
.status(HttpStatus.OK)
.payload(payload));
//Forward the payload into a internal JMS
send(action -> action.endpoint(jms).payload(payload));
}
// ISSUE OCCURS HERE
private ApplyTestBehaviorAction verifyForwardToInternalJms() {
return applyBehavior(new AbstractTestBehavior(){
@Override
public void apply() {
receive(action -> action.endpoint(jms).payload("{\"foo\": 123}"));
}
});
}
private ApplyTestBehaviorAction simulateThirdPartyRestApi() {
return applyBehavior(new AbstractTestBehavior(){
@Override
public void apply() {
http(http -> http.server(httpServer)
.receive()
.post("/bar"));
http(http -> http.server(httpServer)
.respond()
.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.payload(payload));
}
});
}
}
```
Failing test case - without behavior wrapper:
```java
public class TodoListIT extends TestNGCitrusTestRunner {
@Autowired
private HttpClient httpClient;
@Autowired
private HttpServer httpServer;
@Autowired
private JmsEndpoint jms;
private String payload = "{\"foo\": 123}";
@Test
@CitrusTest
public void testGet() {
async().actions(
simulateThirdPartyRestApi(),
verifyForwardToInternalJms());
simulateSUT();
}
private void simulateSUT() {
//Request a payload from a third party REST API
http(http -> http
.client(httpClient)
.send()
.post("/bar"));
http(http -> http
.client(httpClient)
.receive()
.response()
.status(HttpStatus.OK)
.payload(payload));
//Forward the payload into a internal JMS
send(action -> action.endpoint(jms).payload(payload));
}
// ISSUE OCCURS HERE
private TestAction verifyForwardToInternalJms() {
return receive(action -> action.endpoint(jms).payload("{\"foo\": 123}"));
}
private ApplyTestBehaviorAction simulateThirdPartyRestApi() {
return applyBehavior(new AbstractTestBehavior(){
@Override
public void apply() {
http(http -> http.server(httpServer)
.receive()
.post("/bar"));
http(http -> http.server(httpServer)
.respond()
.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.payload(payload));
}
});
}
}
```
Config:
```java
@Configuration
public class EndpointConfig {
@Bean
public HttpClient todoClient() {
return CitrusEndpoints.http()
.client()
.requestUrl("http://localhost:8080")
.build();
}
@Bean
public HttpServer todoServer() {
return CitrusEndpoints.http()
.server()
.port(8080)
.timeout(120000L)
.autoStart(true)
.build();
}
@Bean
public JmsEndpoint createTopicJmsEndpoint() throws JMSException {
return CitrusEndpoints.jms()
.asynchronous()
.destination("foo")
.connectionFactory(new ActiveMQConnectionFactory("tcp://localhost:61616"))
.timeout(120000)
.pubSubDomain(true)
.autoStart(true)
.build();
}
}
```
JMS Broker setup:
```xml
5.15.2
...
org.apache.activemq.tooling
activemq-maven-plugin
${activemq.version}
false
log4j.configuration
log4j.properties
io.fabric8
docker-maven-plugin
0.26.0
default
true
activemq-broker
consol/activemq-5.12:latest
alias
61616:61616
8161:8161
Broker startup
http://localhost:8161
GET
200
500
true
green
```
**Additional Information**
1. The original test case was reconstructed with an IBM-MQ.
2. If I remove the async container and use a simple `TestAction` for my jms action, the test passes! So without async it doesn't matter if I use a behavior or not.
```java
public class TodoListIT extends TestNGCitrusTestRunner {
@Autowired
private HttpClient httpClient;
@Autowired
private HttpServer httpServer;
@Autowired
private JmsEndpoint jms;
private String payload = "{\"foo\": 123}";
@Test
@CitrusTest
public void testGet() {
// SUT Simulation:
// Request a payload from a third party REST API
http(http -> http
.client(httpClient)
.send()
.post("/bar")
.fork(true));
simulateThirdPartyRestApi();
// SUT Simulation:
// Receive the payload from the third party REST API
http(http -> http
.client(httpClient)
.receive()
.response()
.status(HttpStatus.OK)
.payload(payload));
// SUT Simulation:
// Forward the payload into a internal JMS
send(action -> action.endpoint(jms).payload(payload));
verifyForwardToInternalJms();
}
private TestAction verifyForwardToInternalJms() {
return receive(action -> action.endpoint(jms).payload("{\"foo\": 123}"));
}
// private ApplyTestBehaviorAction verifyForwardToInternalJms() {
// return applyBehavior(new AbstractTestBehavior(){
// @Override
// public void apply() {
// receive(action -> action.endpoint(jms).payload("{\"foo\": 123}"));
// }
// });
//
// }
private ApplyTestBehaviorAction simulateThirdPartyRestApi() {
return applyBehavior(new AbstractTestBehavior(){
@Override
public void apply() {
http(http -> http.server(httpServer)
.receive()
.post("/bar"));
http(http -> http.server(httpServer)
.respond()
.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.payload(payload));
}
});
}
}
```
Contributor guide
Research direction
Start with the async().actions setup in the provided TestNGCitrusTestRunner example and compare the wrapped and unwrapped verifyForwardToInternalJms() cases. Reproduce the failure with the JMS endpoint configuration, then identify why both forms do not behave equivalently; done means the unwrapped TestAction passes in the async container as it does without async.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- testing
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 30/100