Testing prose.com
A good test suite is key to enable a team to ship with confidence. This is the stack that made tests first class citizens at Prose front-end.
Either you start writing tests from day 1 or you try to grow the test coverage of an existing web application. It is very common to get stuck with so many options and not know where to start. We’ve also been there at Prose and had to sweat to get to a test suite that we are proud of. Here we would like to share what worked for us and we hope it will help those who are struggling like we were.
I’m not getting into why you should write tests, there are enough good articles out there. This is more like an overview of the important pieces that you will need to combine to test a modern front-end application. We use React and I might cite React for practical examples, but most of the concepts should apply to any browser JavaScript application. In fact you should keep your tests as framework-agnostic as possible because users don’t care what technology you’ve chosen for you app.
Step 1 — Know your tests
First of all, you should know that your tests are going to be a combination of different testing techniques. If you want to know more about the differences between end to end, integration, unit and static testing, I recommend you follow an online course on the subject. I personally like https://testingjavascript.com which has high-quality paid content. They also have this nice diagram for the “Testing Trophy”.
We have the following stack:
- End to End: Cypress with cypress-testing-library
- Integration and Unit: Jest with react-testing-library
- Static: ESLint with Airbnb rules
Step 2 — Write your tests
Here I would like to share some tips concerning each testing category that worked pretty well for us.
End to end
Generally those tests are very intuitive to write. I like to write at least one test doing the happy path of a new feature, and maybe some other key flows. Keep in mind that you should only assert very key essential content and functionality, as you don’t want tests to break every time you make the smallest change. Going from the home page to checkout should be your first end to end test.
You should also define some Cypress commands for the more common use cases. We have commands to create and authenticate users, make a consultation and orders. Those are also very useful to get to a certain point to the website, and then you take the hand on the browser and continue your tests.
Another very important point is to strongly avoid mocking. End to End is the only place you will really be sure your app is really connecting to the outside world. For example, if you use Stripe you can easily get into the iframe and type a test card:
cy
.get('.StripeElement iframe')
.then($iframe => {
const $body = $iframe.contents().find('body');
cy.wrap($body)
.find('.CardField-number input')
.clear({ force: true })
.type('4242424242424242');
})
Unit and Integration
I like to think the unit tests are more adapted to utility code that we use through the application. Typically, those tests will cover data manipulation functions, hooks or other utility code. You want this code to be very stable so you write very precise tests that will cover all details. Code that could be published to npm if they were not too application specific are good candidates for unit testing.
The common notion of writing a lot of unit tests is heavily challenged today, as you don’t want to break your tests every time you change something in your code. This is where Integration tests come handy. What it means in practice is that you are going to run a reasonably big chunk of your application while mocking all external interactions like API calls or Stripe components. In integration tests testing-library comes in very handy, because it enables you to directly manipulate the DOM and be very close to what you are really shipping to the user.
Note for React/Redux users: I do not recommend unit testing most of your Redux elements like selectors, reducers or actions. You will end up with low level tests that are tedious to write and break too often. Instead if you create integration tests around your main containers you’ll get all your selectores and reducers being tested in a more maintainable way. The same is for mostly static pages, just check that some content is there and the CTA is there.
Static
This is pretty straightforward: a minimum set of ESLint rules are mandatory for avoiding typos and other simple mistakes. You can also go with the full set of Airbnb rules from the beginning or as your team grows. One great advantage of strict lint rules is to enforce common rules for formatting and best-practices across all members of the team, making for more useful code reviews. This means no more “Missing semicolon here”, allowing the team to focus more the code architecture.
At Prose, we don’t use static typing check like Typescript, but we enforce declaring PropTypes for our components instead. This is enough for us but I think it heavily depends on the application and the team.
Step 3 — Make your tests visible
I strongly recommend both your test results and coverage to be visible at every PR. This is really a key point if you want to keep your code coverage high. We’ve seen a big increase in our test coverage as we start not only running the tests on CI, but also reporting code coverage. We are using CodeCov, but check for the tool that suits you best. You should be able to see green lights when the code on your PR is covered.
Another nice tip is to combine coverage reports. CodeCov allows you to push multiple artifacts during a CI run and it will automatically merge them for you. Jest coverage reporting is pretty well known, but collecting coverage with Cypress is pretty straightforward as well.
Conclusion
After setting up all those elements, you will have to effectively write your tests, but don’t be overwhelmed, start with something simple that will get you some coverage and try having a good coverage in each PR. With time you will get a better feeling of what tests are useful and what are not and you may delete some useless tests. This is the craft part of software engineering that you build with experience.
I hope you found this article interesting and it will save you some time while setting up your testing stack. I’ve tried to list all tests that are generic, but let me know if you think something is missing. Happy testing!