Search code examples
pact

Using pact for a service with a sequence of requests and responses


Here is my use case:

My Service is: ServiceA.
It depends on the following services: ServicesB and ServiceC.

ServiceA sends a POST request to ServiceB with some authentication details (username+password) and ServiceB replies back with a json document which has a sessionId.

Request:

POST /authenticate
{
    "username": "_at_api",
    "password": "xxx"
}

Response:

{
    "sessionId": "axy235da7ad5a24abeb3e7fbb85d0ef45f"
}

The above sessionId is used for all api calls from ServiceA to ServiceC.

ServiceA requests to serviceC to start a job using a POST request and serviceC returns with a job id(alphanumeric).

Request:

POST /jobs/local/start
Header: Authentication: axy235da7ad5a24abeb3e7fbb85d0ef45f
{
    ...
}

Response:

{
    "status": "RUNNING",
    "jobId": "a209016e3fdf4425ea6e5846b8a46564abzt"
}

ServiceA keeps polling serviceC for the completion of the job using the jobId returned above:

Request:

GET /jobs/status/a209016e3fdf4425ea6e5846b8a46564abzt
Header: Authentication: axy235da7ad5a24abeb3e7fbb85d0ef45f

Response:

{
    "status": "RUNNING"
}

The polling continues until the status is returned as COMPLETED or FAILED.

Response:

{
    "status": "COMPLETED"
}

How can I use Pact to test serviceA?

My plan is to use only unit tests and contract tests to achieve code coverage of more than 90%. Is it a good idea, or do I need to have a separate tests using virtual servers? My understanding is that Pact is a superset of virtual server (example: mountebank) and everything which a virtual server can do, Pact can do. And so I do not need a separate Component testing. Also, it looks like Contract testing completely replaces end-to-end testing, so I do not need end-to-end testing as well. Is this right?


Solution

  • Also, it looks like Contract testing completely replaces end-to-end testing, so I do not need end-to-end testing as well. Is this right?

    No. Contract testing is not functional testing (see this excellent article, with the same title)

    What is contract testing?

    Contract testing is about testing whether two components are able to communicate.

    Consider a contract between a house and a postal worker: The postal worker needs to know that they can approach the house and deliver post (and, that sometimes they may be unable to do this - perhaps the mailbox is full).

    From the postal worker's perspective, the contract looks like this:

    • Find postbox (with a success and fail case)
    • Deliver post to postbox (with a success and fail case)

    Note that the postal worker doesn't know anything about the implementation of the postbox. Perhaps there are multiple reasons that delivering to the postbox might fail - maybe the door is jammed, maybe the box is full, maybe the post is too big to fit in it.

    In this hypothetical case, our postal worker doesn't do anything different in those cases - they just fail to deliver. So, from the perspective of the contract, the reason for the failure is irrelevant. The contract - that the worker can try to deliver post, and that they can be successful or unsuccessful - can be tested without enumerating all the possible reasons for failure.

    See the article linked above for a more detailed example, but to quote from the end of it:

    Contracts should be about catching:

    • bugs in the consumer
    • misunderstanding from the consumer about end-points or payload
    • breaking changes by the provider on end-points or payload

    A really nice feature of Pact is that you can test multiple contracts against only the bits of communication that they rely on.

    Note that the consumer contract tests only describe communication that the consumer needs to make or understand. A contract is not (necessarily) a full API description.

    Ok, but why can't I use contract tests for end to end tests?

    It's possible to use a tool like Pact to replace your end-to-end tests. However, although contract testing has a lot of similarities with the features you'd need for end-to-end testing, contract testing (and Pact in particular) isn't designed for end-to-end testing.

    If you're doing end-to-end testing by extending an existing consumer's tests (say, adding all the possible reasons to failure to the postworker's tests), then it's no longer clear what the contract means. The contract now describes how the communication works along with the behaviour.

    This will cause problems when you start adding more consumers (say, a parcel courier) - do you duplicate all of the failure cases in all the consumers, or do you just keep them in the original consumer tests? If you duplicate the tests, then you have a lot of things to change if you change the behaviour of the provider - and your tests will be brittle. If you don't duplicate the tests, then your end-to-end tests are stuck in one consumer - with all the problems of losing them if you decommission that consumer.

    With pure contract tests, you (ideally) don't have to change anything if you're adding more possible reasons for failure that the consumers already understand.

    There are many other reasons that you'll have headaches if you try this (your tests start relying heavily on exact data, and the meaning of failed verifications and can-i-deploy hooks would change if your tests are end-to-end tests), but the key takeaway is that Pact is not designed as a replacement for end-to-end testing. You can use it that way, but it's not advisable and is likely to lead to painful maintenance.

    How can I use Pact to test serviceA?

    You describe each request separately, using Pact provider state as the prerequisite for each request.

    Additionally, you may find the question on PACT - Using provider state helpful.