Search code examples
javaspring-bootmavencitrus-framework

Citrus Framework - Problem with SUT communicating with other services


I have a set of microservices implemented via Spring Boot that communicate via HTTP REST. I am attempting to set up integration tests using the Citrus Framework. I have successfully written and executed integration tests for some of the services, but am struggling with a more complicated use case.

The scenario is as follows:

I have a notional "mission planning" application that involves 3 services: a mission planner, route generator and a route assessor. The 2 route services are called by the mission planner to do some of the work necessary to plan a mission. The route services make no REST calls.

I have set up my maven POM file to run the integration test when the Maven verify step is performed. The failsafe and Spring Boot maven plugins are configured to do this.

The integration tests for the route services work as expected, but the test for the mission planner does not. I suspect one possible cause is that the route services have not been started. So, after a little research, I inserted calls in the integration test logic to start the 2 services before doing the test work, as well as stopping them afterwards.

I'm not 100% sure whether I have taken the correct steps to start the services properly. I'm also curious whether there is a way to know whether they have actually started successfully. I can't find any documentation that addresses this.

In any case, either they are not being started, or there is something wrong with their configuration, or there is something else causing issues. When I run the test, it fails with an indication that the mission planner is unable to connect to the route generator (which is called first).

Here are the networking details:

  • Everything is currently running on localhost.
  • There is NO SSL/TLS involved currently.
  • The route generator will be listening on port 9110.
  • The route assessor will be listening on port 9120.
  • The mission planner is configured to access the route services using the above ports.

My workplace does use a HTTP proxy - would this affect localhost communications?

Mission planner

EndpointConfig.java:

@Configuration
public class EndpointConfig {
  @Bean
  public HttpClient missionPlannerHTTPClient() {
    return CitrusEndpoints.http().client().requestUrl("http://localhost:9100").build();
  }
  @Bean
  public Server routeGeneratorServer() {
    return CitrusEndpoints.http().server().port(9110).timeout(10000).build();
  }
  @Bean
  public Server routeAssessorServer() {
    return CitrusEndpoints.http().server().port(9120).timeout(10000).build();
  }
}

My limited understanding is that these beans should provide HTTP servers running on the specified ports. A huge unknown for me, though, is how do I actually get the respective services to run? How do I associate them with these endpoints?

Should I be "mocking" the route services instead? I would assume I would do something like the following:

[snippet from Citrus Framework user guide https://citrusframework.org/citrus/reference/2.8.0/html/index.html#http-rest-server]

http().server("helloHttpServer")
          .receive()
          .post("/test")
          .payload("<testRequestMessage>" +
                    "<text<Hello HttpServer</text>" +
                  "</testRequestMessage>")
          .contentType("application/xml")
          .accept("application/xml, */*")
          .header("X-CustomHeaderId", "${custom_header_id}")
          .header("Authorization", "Basic c29tZVVzZXJuYW1lOnNvbWVQYXNzd29yZA==")
          .extractFromHeader("X-MessageId", "message_id");
http().server("helloHttpServer")
          .send()
          .response(HttpStatus.OK)
          .payload("<testResponseMessage>" +
                    "<text<Hello Citrus</text>" +
                  "</testResponseMessage>")
          .version("HTTP/1.1")
          .contentType("application/xml")
          .header("X-CustomHeaderId", "${custom_header_id}")
          .header("X-MessageId", "${message_id}");

Not at all sure how much of everything shown here I actually need. I don't understand most of the last parts of these two calls.

Mocking the non-SUT services makes sense for some forms of testing, but then how would I do an end-to-end test, where all of the services in a given usage chain are real?

MissionPlannerIT.java:

public class MissionPlannerIT extends JUnit4CitrusTest {

  @Qualifier("routeGeneratorServer")
  private HttpServer routeGeneratorServer;
  @Qualifier("routeAssessorServer")
  private HttpServer routeAssessorServer;

  @Autowired
  private HttpClient missionPlannerHTTPClient;

  @Test
  @CitrusTest
  // @formatter:off
  public void testPlanMission(@CitrusResource TestRunner runner) {
    runner.description("Test mission planning 'plan mission' REST endpoint");

    // Start route generator and route assessor.
    runner.start(routeGeneratorServer);
    runner.start(routeAssessorServer);

    // Call mission planner to plan a mission.
    // Note: There is no request body (payload).
    runner.http(builder -> builder
        .client(missionPlannerHTTPClient)
        .send()
        .post("/v1/missionServices/missionPlanning/planMission"));

    // Expect a success response.
    runner.http(builder -> builder
        .client(missionPlannerHTTPClient)
        .receive()
        .response(HttpStatus.OK)
        .messageType(MessageType.JSON)
        .payload(new ClassPathResource("templates/plan-mission-response.json")));

    // Shut down route services.
    runner.stop(routeGeneratorServer);
    runner.stop(routeAssessorServer);
  }
  // @formatter:on

}

It's not clear to me why the 2 servers are annotated with @Qualifier instead of @Autowired.

pom.xml:

  <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <configuration>
      <systemProperties>
        <system.under.test.mode>${system.under.test.mode}</system.under.test.mode>
      </systemProperties>
    </configuration>
    <executions>
      <execution>
        <id>integration-tests</id>
        <goals>
          <goal>integration-test</goal>
          <goal>verify</goal>
        </goals>
      </execution>
    </executions>
  </plugin>

  <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
      <mainClass>edu.mit.ll.mission_services.service.mission_planner.ApplicationMain</mainClass>
      <useTestClasspath>false</useTestClasspath>
    </configuration>
    <executions>
      <execution>
        <id>pre-integration-test</id>
        <goals>
          <goal>start</goal>
        </goals>
      </execution>
      <execution>
        <id>post-integration-test</id>
        <goals>
          <goal>stop</goal>
        </goals>
      </execution>
    </executions>
  </plugin>

Console log:

$ mvn verify
...
[INFO] --- spring-boot-maven-plugin:2.1.6.RELEASE:start (pre-integration-test) @ mission-planner ---
...

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.6.RELEASE)

[INFO]
[INFO] --- maven-failsafe-plugin:2.22.2:integration-test (default) @ mission-planner ---
[INFO]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running edu.mit.ll.mission_services.service.mission_planner.MissionPlannerIT
[WARNING]
org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://localhost:9110/v1/missionServices/missionPlanning/generateRoute": Connect to localhost:9110 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused: connect; nested exception is org.apache.http.conn.HttpHostConnectException: Connect to localhost:9110 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused: connect
    at org.springframework.web.client.RestTemplate.doExecute (RestTemplate.java:744)
    at org.springframework.web.client.RestTemplate.execute (RestTemplate.java:670)
    at org.springframework.web.client.RestTemplate.postForEntity (RestTemplate.java:445)
    at edu.mit.ll.mission_services.service.mission_planner.service.MissionPlanner.postGenerateRoute (MissionPlanner.java:210)
    at edu.mit.ll.mission_services.service.mission_planner.service.MissionPlanner.planMission (MissionPlanner.java:143)
    at edu.mit.ll.mission_services.service.mission_planner.controller.MissionPlannerController$Runner.executeTask (MissionPlannerController.java:51)
    at edu.mit.ll.mission_services.common.util.ITask.run (ITask.java:37)
    at java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:624)
    at java.lang.Thread.run (Thread.java:748)
Caused by: org.apache.http.conn.HttpHostConnectException: Connect to localhost:9110 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused: connect
    at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect (DefaultHttpClientConnectionOperator.java:156)
    at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect (PoolingHttpClientConnectionManager.java:374)
    at org.apache.http.impl.execchain.MainClientExec.establishRoute (MainClientExec.java:393)
    at org.apache.http.impl.execchain.MainClientExec.execute (MainClientExec.java:236)
    at org.apache.http.impl.execchain.ProtocolExec.execute (ProtocolExec.java:186)
    at org.apache.http.impl.execchain.RetryExec.execute (RetryExec.java:89)
    at org.apache.http.impl.execchain.RedirectExec.execute (RedirectExec.java:110)
    at org.apache.http.impl.client.InternalHttpClient.doExecute (InternalHttpClient.java:185)
    at org.apache.http.impl.client.CloseableHttpClient.execute (CloseableHttpClient.java:83)
    at org.apache.http.impl.client.CloseableHttpClient.execute (CloseableHttpClient.java:56)
    at org.springframework.http.client.HttpComponentsClientHttpRequest.executeInternal (HttpComponentsClientHttpRequest.java:87)
    at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal (AbstractBufferingClientHttpRequest.java:48)
    at org.springframework.http.client.AbstractClientHttpRequest.execute (AbstractClientHttpRequest.java:53)
    at org.springframework.web.client.RestTemplate.doExecute (RestTemplate.java:735)
    at org.springframework.web.client.RestTemplate.execute (RestTemplate.java:670)
    at org.springframework.web.client.RestTemplate.postForEntity (RestTemplate.java:445)
    at edu.mit.ll.mission_services.service.mission_planner.service.MissionPlanner.postGenerateRoute (MissionPlanner.java:210)
    at edu.mit.ll.mission_services.service.mission_planner.service.MissionPlanner.planMission (MissionPlanner.java:143)
    at edu.mit.ll.mission_services.service.mission_planner.controller.MissionPlannerController$Runner.executeTask (MissionPlannerController.java:51)
    at edu.mit.ll.mission_services.common.util.ITask.run (ITask.java:37)
    at java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:624)
    at java.lang.Thread.run (Thread.java:748)
Caused by: java.net.ConnectException: Connection refused: connect
    at java.net.DualStackPlainSocketImpl.waitForConnect (Native Method)
    at java.net.DualStackPlainSocketImpl.socketConnect (DualStackPlainSocketImpl.java:85)
    at java.net.AbstractPlainSocketImpl.doConnect (AbstractPlainSocketImpl.java:350)
    at java.net.AbstractPlainSocketImpl.connectToAddress (AbstractPlainSocketImpl.java:206)
    at java.net.AbstractPlainSocketImpl.connect (AbstractPlainSocketImpl.java:188)
    at java.net.PlainSocketImpl.connect (PlainSocketImpl.java:172)
    at java.net.SocksSocketImpl.connect (SocksSocketImpl.java:392)
    at java.net.Socket.connect (Socket.java:589)
    at org.apache.http.conn.socket.PlainConnectionSocketFactory.connectSocket (PlainConnectionSocketFactory.java:75)
    at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect (DefaultHttpClientConnectionOperator.java:142)
    at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect (PoolingHttpClientConnectionManager.java:374)
    at org.apache.http.impl.execchain.MainClientExec.establishRoute (MainClientExec.java:393)
    at org.apache.http.impl.execchain.MainClientExec.execute (MainClientExec.java:236)
    at org.apache.http.impl.execchain.ProtocolExec.execute (ProtocolExec.java:186)
    at org.apache.http.impl.execchain.RetryExec.execute (RetryExec.java:89)
    at org.apache.http.impl.execchain.RedirectExec.execute (RedirectExec.java:110)
    at org.apache.http.impl.client.InternalHttpClient.doExecute (InternalHttpClient.java:185)
    at org.apache.http.impl.client.CloseableHttpClient.execute (CloseableHttpClient.java:83)
    at org.apache.http.impl.client.CloseableHttpClient.execute (CloseableHttpClient.java:56)
    at org.springframework.http.client.HttpComponentsClientHttpRequest.executeInternal (HttpComponentsClientHttpRequest.java:87)
    at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal (AbstractBufferingClientHttpRequest.java:48)
    at org.springframework.http.client.AbstractClientHttpRequest.execute (AbstractClientHttpRequest.java:53)
    at org.springframework.web.client.RestTemplate.doExecute (RestTemplate.java:735)
    at org.springframework.web.client.RestTemplate.execute (RestTemplate.java:670)
    at org.springframework.web.client.RestTemplate.postForEntity (RestTemplate.java:445)
    at edu.mit.ll.mission_services.service.mission_planner.service.MissionPlanner.postGenerateRoute (MissionPlanner.java:210)
    at edu.mit.ll.mission_services.service.mission_planner.service.MissionPlanner.planMission (MissionPlanner.java:143)
    at edu.mit.ll.mission_services.service.mission_planner.controller.MissionPlannerController$Runner.executeTask (MissionPlannerController.java:51)
    at edu.mit.ll.mission_services.common.util.ITask.run (ITask.java:37)
    at java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:624)
    at java.lang.Thread.run (Thread.java:748)
09:00:46.576 [main] ERROR com.consol.citrus.report.LoggingReporter - TEST FAILED MissionPlannerIT.testPlanMission <edu.mit.ll.mission_services.service.mission_planner> Nested exception is:
com.consol.citrus.exceptions.TestCaseFailedException: Failed to validate JSON text:
{"timestamp":"2020-02-06T14:00:46.436+0000","status":503,"error":"Service Unavailable","message":"Service Unavailable","path":"/v1/missionServices/missionPlanning/planMission"} Number of JSON entries not equal for element: '$.', expected '1' but was '5'
        at com.consol.citrus.TestCase.executeAction(TestCase.java:227) [citrus-core-2.8.0.jar:?]
        at com.consol.citrus.dsl.runner.DefaultTestRunner.run(DefaultTestRunner.java:188) ~[citrus-java-dsl-2.8.0.jar:?]
        at com.consol.citrus.dsl.runner.DefaultTestRunner.http(DefaultTestRunner.java:540) [citrus-java-dsl-2.8.0.jar:?]
        at edu.mit.ll.mission_services.service.mission_planner.MissionPlannerIT.testPlanMission(MissionPlannerIT.java:44) ~[test-classes/:?]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_191]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_191]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_191]
        at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_191]
        at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:282) ~[spring-core-5.1.8.RELEASE.jar:5.1.8.RELEASE]
        at com.consol.citrus.dsl.junit.JUnit4CitrusTest.invokeTestMethod(JUnit4CitrusTest.java:99) [citrus-java-dsl-2.8.0.jar:?]
        at com.consol.citrus.dsl.junit.JUnit4CitrusTest.run(JUnit4CitrusTest.java:70) [citrus-java-dsl-2.8.0.jar:?]
        at com.consol.citrus.junit.CitrusJUnit4Runner$InvokeRunMethod.evaluate(CitrusJUnit4Runner.java:217) [citrus-core-2.8.0.jar:?]
        at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74) [spring-test-5.1.8.RELEASE.jar:5.1.8.RELEASE]
        at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84) [spring-test-5.1.8.RELEASE.jar:5.1.8.RELEASE]
        at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75) [spring-test-5.1.8.RELEASE.jar:5.1.8.RELEASE]
        at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86) [spring-test-5.1.8.RELEASE.jar:5.1.8.RELEASE]
        at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84) [spring-test-5.1.8.RELEASE.jar:5.1.8.RELEASE]
        at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) [junit-4.12.jar:4.12]
        at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251) [spring-test-5.1.8.RELEASE.jar:5.1.8.RELEASE]
        at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97) [spring-test-5.1.8.RELEASE.jar:5.1.8.RELEASE]
        at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) [junit-4.12.jar:4.12]
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) [junit-4.12.jar:4.12]
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) [junit-4.12.jar:4.12]
        at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) [junit-4.12.jar:4.12]
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) [junit-4.12.jar:4.12]
        at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) [spring-test-5.1.8.RELEASE.jar:5.1.8.RELEASE]
        at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) [spring-test-5.1.8.RELEASE.jar:5.1.8.RELEASE]
        at org.junit.runners.ParentRunner.run(ParentRunner.java:363) [junit-4.12.jar:4.12]
        at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190) [spring-test-5.1.8.RELEASE.jar:5.1.8.RELEASE]
        at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:365) [surefire-junit4-2.22.2.jar:2.22.2]
        at org.apache.maven.surefire.junit4.JUnit4Provider.executeWithRerun(JUnit4Provider.java:273) [surefire-junit4-2.22.2.jar:2.22.2]
        at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:238) [surefire-junit4-2.22.2.jar:2.22.2]
        at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:159) [surefire-junit4-2.22.2.jar:2.22.2]
        at org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:384) [surefire-booter-2.22.2.jar:2.22.2]
        at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:345) [surefire-booter-2.22.2.jar:2.22.2]
        at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:126) [surefire-booter-2.22.2.jar:2.22.2]
        at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:418) [surefire-booter-2.22.2.jar:2.22.2]
Caused by: com.consol.citrus.exceptions.ValidationException: Failed to validate JSON text:
{"timestamp":"2020-02-06T14:00:46.436+0000","status":503,"error":"Service Unavailable","message":"Service Unavailable","path":"/v1/missionServices/missionPlanning/planMission"} Number of JSON entries not equal for element: '$.', expected '1' but was '5'
        at com.consol.citrus.validation.json.JsonTextMessageValidator.validateMessage(JsonTextMessageValidator.java:127) ~[citrus-core-2.8.0.jar:?]
        at com.consol.citrus.validation.json.JsonTextMessageValidator.validateMessage(JsonTextMessageValidator.java:62) ~[citrus-core-2.8.0.jar:?]
        at com.consol.citrus.validation.AbstractMessageValidator.validateMessage(AbstractMessageValidator.java:46) ~[citrus-core-2.8.0.jar:?]
        at com.consol.citrus.actions.ReceiveMessageAction.validateMessage(ReceiveMessageAction.java:231) ~[citrus-core-2.8.0.jar:?]
        at com.consol.citrus.actions.ReceiveMessageAction.doExecute(ReceiveMessageAction.java:128) ~[citrus-core-2.8.0.jar:?]
        at com.consol.citrus.actions.AbstractTestAction.execute(AbstractTestAction.java:42) ~[citrus-core-2.8.0.jar:?]
        at com.consol.citrus.dsl.actions.DelegatingTestAction.doExecute(DelegatingTestAction.java:54) ~[citrus-java-dsl-2.8.0.jar:?]
        at com.consol.citrus.actions.AbstractTestAction.execute(AbstractTestAction.java:42) ~[citrus-core-2.8.0.jar:?]
        at com.consol.citrus.TestCase.executeAction(TestCase.java:220) ~[citrus-core-2.8.0.jar:?]
        ... 36 more
Caused by: java.lang.IllegalArgumentException: Number of JSON entries not equal for element: '$.', expected '1' but was '5'
        at org.springframework.util.Assert.isTrue(Assert.java:118) ~[spring-core-5.1.8.RELEASE.jar:5.1.8.RELEASE]
        at com.consol.citrus.validation.json.JsonTextMessageValidator.validateJson(JsonTextMessageValidator.java:170) ~[citrus-core-2.8.0.jar:?]
        at com.consol.citrus.validation.json.JsonTextMessageValidator.validateMessage(JsonTextMessageValidator.java:115) ~[citrus-core-2.8.0.jar:?]
        at com.consol.citrus.validation.json.JsonTextMessageValidator.validateMessage(JsonTextMessageValidator.java:62) ~[citrus-core-2.8.0.jar:?]
        at com.consol.citrus.validation.AbstractMessageValidator.validateMessage(AbstractMessageValidator.java:46) ~[citrus-core-2.8.0.jar:?]
        at com.consol.citrus.actions.ReceiveMessageAction.validateMessage(ReceiveMessageAction.java:231) ~[citrus-core-2.8.0.jar:?]
        at com.consol.citrus.actions.ReceiveMessageAction.doExecute(ReceiveMessageAction.java:128) ~[citrus-core-2.8.0.jar:?]
        at com.consol.citrus.actions.AbstractTestAction.execute(AbstractTestAction.java:42) ~[citrus-core-2.8.0.jar:?]
        at com.consol.citrus.dsl.actions.DelegatingTestAction.doExecute(DelegatingTestAction.java:54) ~[citrus-java-dsl-2.8.0.jar:?]
        at com.consol.citrus.actions.AbstractTestAction.execute(AbstractTestAction.java:42) ~[citrus-core-2.8.0.jar:?]
        at com.consol.citrus.TestCase.executeAction(TestCase.java:220) ~[citrus-core-2.8.0.jar:?]
        ... 36 more
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 13.907 s <<< FAILURE! - in edu.mit.ll.mission_services.service.mission_planner.MissionPlannerIT
[ERROR] testPlanMission(edu.mit.ll.mission_services.service.mission_planner.MissionPlannerIT)  Time elapsed: 9.88 s  <<< ERROR!
com.consol.citrus.exceptions.TestCaseFailedException:
Failed to validate JSON text:
{"timestamp":"2020-02-06T14:00:46.436+0000","status":503,"error":"Service Unavailable","message":"Service Unavailable","path":"/v1/missionServices/missionPlanning/planMission"} Number of JSON entries not equal for element: '$.', expected '1' but was '5'
        at edu.mit.ll.mission_services.service.mission_planner.MissionPlannerIT.testPlanMission(MissionPlannerIT.java:44)
Caused by: com.consol.citrus.exceptions.ValidationException:
Failed to validate JSON text:
{"timestamp":"2020-02-06T14:00:46.436+0000","status":503,"error":"Service Unavailable","message":"Service Unavailable","path":"/v1/missionServices/missionPlanning/planMission"} Number of JSON entries not equal for element: '$.', expected '1' but was '5'
        at edu.mit.ll.mission_services.service.mission_planner.MissionPlannerIT.testPlanMission(MissionPlannerIT.java:44)
Caused by: java.lang.IllegalArgumentException: Number of JSON entries not equal for element: '$.', expected '1' but was '5'
        at edu.mit.ll.mission_services.service.mission_planner.MissionPlannerIT.testPlanMission(MissionPlannerIT.java:44)

[INFO]
[INFO] Results:
[INFO]
[ERROR] Errors:
[ERROR]   MissionPlannerIT>JUnit4CitrusTest.run:70->JUnit4CitrusTest.invokeTestMethod:102 » TestCaseFailed
[INFO]
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0
[INFO]
[INFO]
[INFO] --- maven-failsafe-plugin:2.22.2:integration-test (integration-tests) @ mission-planner ---
[INFO] Skipping execution of surefire because it has already been run for this configuration
[INFO]
[INFO] --- spring-boot-maven-plugin:2.1.6.RELEASE:stop (post-integration-test) @ mission-planner ---
[INFO] Stopping application...
[INFO]
[INFO] --- maven-failsafe-plugin:2.22.2:verify (default) @ mission-planner ---
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for Mission Services - REST 0.0.1-SNAPSHOT:
[INFO]
[INFO] Mission Services - REST ............................ SUCCESS [  2.475 s]
[INFO] Common Utilities ................................... SUCCESS [  3.778 s]
[INFO] Mission Services Message Types ..................... SUCCESS [  1.823 s]
[INFO] Security ........................................... SUCCESS [  1.262 s]
[INFO] Mission Planner Client ............................. SUCCESS [  4.679 s]
[INFO] Mission Planner Service ............................ FAILURE [ 48.212 s]
[INFO] Route Generator Service ............................ SKIPPED
[INFO] Route Assessor Service ............................. SKIPPED
[INFO] UAV Service ........................................ SKIPPED
[INFO] Correlator Service ................................. SKIPPED
[INFO] Correlator Client .................................. SKIPPED
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  01:03 min
[INFO] Finished at: 2020-02-06T09:00:50-05:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-failsafe-plugin:2.22.2:verify (default) on project mission-planner: There are test failures.
[ERROR]
[ERROR] Please refer to C:\Users\JO24447\workspace\REST_RST_Service\mission-services\mission-planner\target\failsafe-reports for the individual test results.
[ERROR] Please refer to dump files (if any exist) [date].dump, [date]-jvmRun[N].dump and [date].dumpstream.
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
[ERROR]
[ERROR] After correcting the problems, you can resume the build with the command
[ERROR]   mvn <goals> -rf :mission-planner

I believe the test failure is reporting that it was expecting the specified JSON message, but instead received the 503 message JSON instead. This threw me off for quite awhile, making me think the problem lie with the mission planner service not being available. I don't think that's the issue at all.

Is there something wrong in how I'm defining/starting the services in the test logic? Could the proxy be getting in the way? Is there some way to guage the health of the route services (assuming they are even started)? Is there configuration missing when starting the other services?

I appreciate that there is huge amount of documentation and examples for the Citrus Framework and it has been very helpful. However, there are a lot of situations and usage scenarios that are not discussed, and that makes it hard to know what to do to succeed.

UPDATE:

The jury is still out, but I may have stumbled upon a solution. If this really does fix the problem, it will be like most typical solutions, once you've figured them out: ridiculously simple.

I was scanning through the Citrus samples on github and finally came upon an example that was similar at least in some respects to my situation. What I found caught my attention and when I tried it out, it seems like I finally got past the problem.

Here's a snippet:

@Autowired
@Qualifier("networkBackendHttpServer")
private HttpServer networkHttpServer;

Note the @Autowired annotation. That's what was missing from my code. Remember I had had musings about that above. If only I had known what the @Qualifer annotation did and did not do.


Solution

  • The solution was simple. I needed to annotate my "server" objects with @Autowired.

    public class MissionPlannerIT extends JUnit4CitrusTest {
    
      @Autowired
      @Qualifier("routeGeneratorServer")
      private HttpServer rgServer;
      @Autowired
      @Qualifier("routeAssessorServer")
      private HttpServer raServer;
    

    I had made the erroneous assumption that the @Qualifier annotation also auto-wired these objects. Heh, nope.