API (integration) testing
Surface area
Facade of an actual, deployed service or API (your service or some external service)
Confidence level
Medium; good enough if you trust there are no adverse side effects or similar
Granularity
Low, you know it works and does what should do for you as a consumer
Pros
Probably the most intuitive and easy way to actually test something
Cons
Side effects from using an actual system
When to do this type of testing?
Your services: Any time you are building something that provides an API, then consider a low degree of these tests to verify complete upfront functionality.
External services: Always mock these, if for no other reason than because your unit tests will be able to do any outside calls without actually creating side effects.
Let it be known that the classic notion of integration testingโtesting several software components at the same timeโis practically useless. That could just be covered by a wider unit test.
A related notion is the one of sociable vs solitary tests.
For the intents and purposes of this chapter, we will think of integration testing and API testing as essentially the same: We test something "from the outside", whether that's an HTTP API or something else matters less.
Because we are going to use an actual system we don't really have to do more than simply use it! It wouldn't not be testing if you'd use Postman or Insomnia on every deployment to call an API with some prepackaged payloads, however, we'd of course not have it automated either. So let's discard that idea even if it's valid to some extent in factual terms.
Running tool-centric tests
What you can do, however, with Postman and Insomnia is to use their CLI counterparts to run the tests during CI:
I don't have any deeper experience with these, but it's definitely possible and makes sense if you already use such API clients to store payloads.
Moving on to our DIY solution...
Writing our own integration testing tool
To do a bit of lightweight integration testing we need two things:
Some kind of evaluator engine.
A set of assertions to test.
For the evaluator, we'll make life a little easier by using Ajv to perform JSON validation on our behalf, which could otherwise easily escalate into a major pain. While fetch
is nowadays native in Node 18 and upwards, for compatibility reasons we will also pull in node-fetch to handle requests.
There are breaking changes between versions 2 and 3 of node-fetch
. If you are using Webpack to bundle your application, consider using the most recent 2-series version of node-fetch
or you may face bundling errors.
While this isn't strictly zero-dependency, it's lightweight enough and allows us the required flexibility to test actual APIs with payloads or assertions that we define without surrendering to specific testing frameworks.
Let's browse the code.
The critical parts are:
runIntegrationTests()
which orchestrates the overall functionality.fetchData()
does just that.test()
usesajv
to compile and validate the provided expected schema with what we got
Note that in the above code we also check if the response is simply "OK" in which case it is accepted as a match (for cases in which you may not actually receive any content, such as with status code 204).
The assertions are in a custom, though fairly flexible, format in which we can set our test names and payloads for any HTTP method.
Using a body
means we pass that actual body in a POST request, while urlParams
are used if we need parameters passed in the URL rather than as a request body.
Lastly, the schema
is your everyday JSON Schema to validate with ajv
.
Are you integration testing someone else's thing?
Doing API (integration) testing in CI of other's services does not make logical sense, as this could lead to cases in which your deployment failed because of a failure elsewhere. And if you are thinking that "well, I mean if the other thing ain't working then there's no real use for me to deploy, right?" then the answer is clear: That's plain mixing it up in the wrong way.
Remember that a test is always of some unit, performing something at that specific moment. It is not a guarantee for correct functionality at any later point, and especially not if other factors are more fluid, such as completely external dependencies and APIs, and that includes any non-application layer (i.e. network, etc.).
In closing
Hopefully, this demystifies integration/API testing a bit for you, and that you see why I argue so much more passionately for unit tests.
Last updated