11

I want to write some test against an REST endpoint which is using a callback parameter. Might sound confusing so here's an explanation:

  • first call is a POST to /publish with the payload:

    {"callbackURL":"http://localhost:609/test"}

  • server replies with 200 and some json

  • the server does an async POST to http://localhost:609/test/status (based on the provided callback URL), with some json payload

I built a test using TestNG and Rest-assured (not mandatory). My problem is how to validate both the initial reply and the message received at the callback URL. A (maybe naive) approach I first thought of is to implement a HTTP server which listens at http://localhost:609/test/status, when something comes it writes to a synchronized object from which the test reads and validates. However, sounds a bit tricky (the test local server will use an inner class, etc.) and fragile. Can anyone think of a better approach?

Peter Thomas
  • 54,465
  • 21
  • 84
  • 248
vicusbass
  • 1,714
  • 2
  • 19
  • 33
  • I need to do this same thing. Did you come up with a solution? – Jamie Bisotti Feb 21 '18 at 14:25
  • No, in the meanwhile I started using Spring Cloud Contracts for the bigger picture – vicusbass Feb 22 '18 at 15:01
  • Approach is correct, `synchronized object from which the test reads and validates` use a `CountDownLatch` with a timeout. awaitability is good framework to use for assertions on async flows. Use [spark-java] for a test endpoint that you can bootstrap within your test. – SudhirR Nov 19 '19 at 07:19

2 Answers2

-1

Starting a local HTTP server out of the test code is a straightforward way. There are plenty of libraries to do it, for example WireMock:

http://wiremock.org/index.html

https://github.com/karatelabs/karate (for Karate see the answer from "Peter Thomas")

It works perfectly during the development, but it is very limited in the real world. The test pipeline is usually not available from outside. It cannot listen for something. The developer of the test works in the local network behind NAT/firewall. He won't be able to listen for requests coming from the staging or production environment (which is running in the DMZ or in the Cloud).

The better solution is to use a mock server. It must listen for a request and record this event. The test asks the mock server for the event in the next step and validates it.

One can use cloud-based mock server, for example Postman or MockLab:

https://www.mocklab.io/docs/stubbing/

https://learning.postman.com/docs/designing-and-developing-your-api/mocking-data/setting-up-mock/

For simple use-case one can develop a mock server manually using any public cloud provider like AWS. The test must deploy a small Lambda function which listen for a request and puts it on S3 storage for validation. AWS most probably won't bill you for this use-case. 1M requests per month are for free:

https://aws.amazon.com/lambda/pricing/

Or one can implement this logic as a part of the server which is being tested.

30thh
  • 10,861
  • 6
  • 32
  • 42
-2

Use Karate. Disclaimer: I am the developer.

Here is an example (with links to the source code) on how you can achieve this: https://twitter.com/KarateDSL/status/1417023536082812935

I'll try to explain the diagram in a simple way.

  • The Test creates a mock HTTP server when the test starts. Here the port is dynamic, but you can very well hard-code it to e.g. 8080 if the client is remote. So the client can call http://hostname/8080/send
  • Note how the mock is able to "talk" to a Java class. And how the Test waits for the Java class to signal a state change - when something calls the Mock.
  • This particular demo uses a message-queue to connect the Mock and the Java class. But you don't need to go that far to just respond to a callback, just complete the CompletableFuture by invoking some method instead of where you see QueueUtils.send() in the example
  • Here the Test itself is making a call to the Mock but in the scenario you describe, you can very well make a call to /publish and then wait for the callback.
  • If the server is remote and not fully in your control, you will need to somehow tell it the URL to call back to. But in the question here, it looks like you can pass the URL in the /publish call itself.
  • And if your server is remote and you cannot "expose" your local machine to the internet or the same network, you can easily run Karate anywhere wrapped in a Docker container or within a cloud instance such as EC2. And note that there are solutions such as ngrok.

Karate and HTTP callbacks

Peter Thomas
  • 54,465
  • 21
  • 84
  • 248