How to Setup a Consumer-Driven Contract Using Pact in a Stencil App
Stencil provides a nice support when you want to write tests for your components and apps. It brakes down tests into two categories.
Unit tests are not run within a browser so that they can be simple and fast. You can use them to test bits of logic in isolation, assuming that you are not really simulating some user experience in a realistic way.
When you want to simulate the behaviour of the app, Stencil provides support for end-to-end tests. They are run in a real browser using Puppeteer. The assumption seems to be that in that case you do not mock anything and just use your actual component in tests.
One drawback of this setup may appear once your component needs to call some API to provide its features. Calling services outside of your control may make your tests flaky and slow. The service might be down at the moment you are trying to run the test and you may waste time debugging issues that do not stem from your code.
Tests written that way may also become way more complicated if the setup of the API is non-trivial. You may need to authenticate using a different service, set up some persistent state so that the API response is what you need, etc.
At the same time if everything works out just fine this approach may give you a lot of confidence. How could we get a similar level of confidence without all the risks and difficulties?
Consumer-driven contracts (CDC) is an approach that seems to be a good fix for the issues I mentioned before. The actual implementation I will show in this article is Pact.
The idea is that you create a set of mock APIs that you use in your frontend tests. Once those pass, the calls that were made to those APIs (here called interactions) are recorded and saved.
Later on, those recorded interactions are used to automatically generate a set of integration tests for the service providing the API. As there is no need for manually retyping the same expectations in the backend tests you get a strong check that the consumer (Stencil component in this case) and the provider (some backend API) behave in mutually compatible ways.
Let’s set up a simplistic component. A todo list will suffice.
You may notice that the component receives a url in the
from property. This might not be the best solution in actual code. Either way, you probably shouldn’t hardcode the url of the API in case you want run against a test / development environment or something.
To setup CDC using Pact we will need to add some dependencies. As stencil uses Jest to run tests we will install a set of utils that will do most of the heavy lifting for us.
npm i jest-pact @pact-foundation/pact --save-dev
Once we have them installed we can write our contract test.
We set up the mock provider in a specific, named state (`Todo list is set`). We define what url we are expecting to hit and what will be the expected response.
Note that we expect the provider to return the
Access-Control-Allow-Origin header. This sets up CORS, as we expect the frontend and the API to be hosted on different servers. That will also be true in our tests, as Pact will run a separate process besides the Node.js that runs Jest and the Puppeteer- controlled Chrome.
We should now be able to run our test.
As an output,
jest-pact will create subdirectory with the recorded interactions.
Running tests to verify the provider
In a more realistic solution we could use a so-called Pact Broker to store our contracts and verify them across projects. For now let’s just create an API in the same monorepo using Express. In this case we will provide an absolute path to the contract files.
We will again need a dependency to
@pact-foundation/pact. We will run our Express app in the test and set up a Verifier that will check all the interactions. We need to tell the Verifier were (on what url) should it expect our app.
We also need to provide a list of state handlers that will bring the application into one of the named states. In our case the only state is the “Todo list is set” state.
The nice thing is that as long as we do not need to provide a new state we will not need to touch these tests again. Any new interactions will be verified using the same code.
I hope I shown that setting up the contract tests can be quite simple and described some of their benefits. Keep in mind that Pact is a polyglot and you can verify providers written in different languages basically the same way.
I have set up an example repo if you want to see a working solution.