Especially if you’re starting with automated tests!
You’ve written the code, you manually tested and it works (maybe, at least for the cases you’ve used).
Maybe you are fed up with bugs appearing in production or you have some annoying coworker always nagging about tests (well… sorry not sorry, it’s for your own good and for my sanity).
Integration tests
For doing this, unit tests can have a smaller scope, so integration is the way to go.
In the front end pick the functionality of a whole page.
Just the page, not the header/sidebar unless you’re testing that.
The best case scenario you would be using something like MirageJS, MSW, or even JSON Server, but mocking fetch is always an option.
In the backend, test a whole route or handler in the controller.
The best case scenario would be using an ORM, with migrations and seeders with the alternative being mocking the repositories you need.
Why test without seeing the code
In other posts, I commented on how it’s easy to end up testing what you know the code’s supposed to do and not what it’s actually doing.
This happens because you get tunnel vision, after all, you already know it works. But then again you’re focused on one thing and not everything, everywhere, all at once.
Testing as if it’s a black box will help you think as the user will. They don’t know that they can’t do this or that or that you’re expecting an array there and not an object.
Testing what you know works is just the beginning, the real fun happens when you start thinking about ways and inputs that are wrong and how they will break your “perfect” code.
I’m not saying you need 100% coverage, but at least the expected mistakes and unhappy paths should be covered after the happy ones. (user without authentication or the requirements needed, inputting the wrong things, going back and forth steps…)
Another moment you can do that is when you’re handed down something without any tests (as I was). Without tests, it makes it harder to refactor and add new features. But knowing you don’t need to see the code to have a sufficient safety net, makes you actually look forward to starting playing around with the code.
How to
Frontend
I’m using and considering you’re also using the Testing Library to test here.
All you need to know from the code is the main component to render, you’ll also probably need a custom render with whatever contexts you use.
Now, all you need to do is open the front end and go step by step on what you’re doing there and put that as code in the test.
Render the component, what should you be seeing? (there’s an extension to help you there.)
If you need to fetch, even if you’re going to mock fetch directly, all you need is to check the devtools in the network tab, check where it’s pointing and with what arguments, body, method… then check what it’s returning and copy paste all of that in a test.
Then whatever action you need to take (click, type…) you do using user-event and check if whatever should be happened next was.
Rinse, repeat and get annoyed when you can’t easily access what you want because everything is divs without accessible roles.
Backend
If you have ORM, migrations, and seeds, then you can use something like supertest (just make sure to test how to have a clean state before each test) and test what you should receive passing calling the path you want.
Depending on how you’re structuring your app and files, sometimes unit testing is the easier and faster way to go.
Usually, you have controller
⇒ service
⇒ repository
⇒ db
.
If you have clear boundaries between those:
- the controller parses the arguments it needs, initiates dependencies, and calls the service
- the service has whatever business rules you need and calls the repository
- repository transform in calls to the DB and returns the data in an object
- the service has more business rules and returns errors or something else
- the controller will return the proper status code with payload depending on what it received
This means that, with proper boundaries, the service can be almost “pure” in the sense that by itself, it should always return the same results given the same inputs.
The test will start mocks for the repository, calls the service, and pass the mocks instead of the original implementations and in turn, you test whatever it received given what you’ve called it with and what you put in the mocks.
Yes, this would be unit testing, but I urge you to ignore the code and test as if it was a black box. Same as with front end, fire the app and check what it would be receiving (from controller, repository) and what it would be returning.
Cover art made in Wombot
I wanted something along the lines of “programmer testing software with eyes covered”
Gotta share here some of the other weird results: