How I do integration test with jest and supertest
The component structure #
This week I'm in the process of moving the test files from a "test" directory to the "src" directory itself. This is a quite established practice inside javascript userland and it has very pleasant effects:
- First, looking at the content folder, I know which files have tests and which not
- In general, tests are more "present" in the day-to-day work
- Having the test close to the implementation makes the require paths much sorter (and less cognitive work to find the test of the implementation and vice-versa) There is no natural place to put "test helper functions" so I discover that most of their functionality could be delivered by using own app business logic functions, so I ended removing some boilerplate code.
Here are the contents of the Step component:
The controller.*.spec.js contains all the integration tests. Since integration test can be quite long, I've made a test file for each controller endpoint. The index.js file contains the business logic (what in Rails we usually call the Service object) and index.spec.js is it's associated test.
I did no changes in how I run the tests since jest test runner look for files ending in .spec.js or .test.js regardless of where they are. Optionally, I can pass to the jest-cli a pattern so, to run all integration test all I have to do is: jest controller And jest will run all controller.*.spec.js files in all the directories.
Testing an express.js app #
This is the require statements of the controller.list-steps.spec.js file of the Step component:
All the heavy work is done by the "supertest" library. It takes an entire express.js app, set up a server and send http request to it.
One thing I like from Javascript is that everything is quite explicit (declarative). For example, if you need to setup a database connection, you have to explicitly say it. So, we start by describing a context scenario (using the jest's "describe" function) and declaring which functions should be running before and after all tests of this file:
Zoom describe-controller.png
Inside the "StepController" scenario we describe another scenario:
Here I use a helper function ("createWorkflow", defined at the end of the test, to reduce the noise) that insert into the database the testing data. An important thing to note is that I don't clear the database, just remove the data I've inserted, because tests in jest are running in parallel, so cleaning the database in one test would make others fail. For the same reason, I've timestamped some data (like user's permissions) to avoid collisions with data from other tests.
Finally, I created the test server (using supertest's request function), made the request and checked the output:
As you can see, the code is quite self explanatory, all with this async goodness. There's this nice "objectContaining" function that allows me to check only some attributes of a big object.
Testing coverage #
One nice feature of jest is that it gives you coverage reports. With this command I can run all test from the Step component:
jest app/components/step --coverage
And it returns back a coverage report, including line numbers of non-tested code:
That's all folks!