net - UK (2020-04)

(Antfer) #1

React Testing Library


The hardest part of finding out what to test is knowing what
your code actually does. This becomes harder the more time
that passes between when you write tests to when you write the
actual code. So I recommend writing tests alongside the
component or even before you write your component.
When you’re doing this you’ll be more clearly able to think about
all of the different outcome possibilities your code offers: what
variables might change? What different return values are possible?
I’ve used an API call in this example because there’s plenty of
variety in what can happen but I’ve still missed out one valuable
test: can you spot which test I haven’t done?

SCOPING OUTCOMES


IN-DEPTH

parameters: {
categories: [“2”]
}
});


Okay, so if this test passes, our code is passing the
category correctly. Great! But does the data actually
reflect that?


expect(data[0]).toEqual(
expect.objectContaining({
categories: [‘2’]
})
)


If this test passes, then it’s all good! We are
successfully able to obtain data with the correct
parameters. Another check to do here is that the
data only contains items with this category and not
any other. I’ll leave that particular task for you to
figure out.
These next two tests verify that we have captured
two very significant branches, or outcomes, of our
code: failures.


TEST 4: RETURN AN EMPTY OBJECT IF NO
DATA WAS RECEIVED
If there hasn’t been any data sent back to us after
the API call, we have returned an array as a fallback
so that it doesn’t result in an exception in our
application. That can subsequently be used by our
UI to provide a fallback – once the API call itself has
been resolved.


it(‘Should return an empty array if no data was received’,
async () => {
mockedAxios.mockResolvedValueOnce({ data: null });
const data = await GetApiData(domain);
expect(mockedAxios).toBeCalledTimes(1);
expect(Array.isArray(data)).toBeTruthy;
})


We’re mocking a data object with a null value here
as we want no values being returned from the API
call. We’re using Array.isArray because that is far more
robust than using isArray, which is an older method
that returns true for a number of different cases
(because JavaScript is weird).


TEST 5: LOG AN ERROR IF THE REQUEST
WAS UNSUCCESSFUL
In this test, I’m just going to check for a
console.log() call but, in a production app, there would
be an integration with some external logging system
that would send an alert to the team.


Our final test will be using our consoleMock from
our initial setup:

it(“Should log an error if the request was unsuccessful”,
async () => {
const error = new Error(“there was an error”);
mockedAxios.mockRejectedValue(error);
await GetApiData(domain);
expect(mockedAxios).toBeCalledTimes(1);
expect(mockedConsole).toBeCalledTimes(1);
expect(mockedConsole).toBeCalledWith(error);
});

Because we’re expecting that an error is thrown by
our code, we need to use the Error object to test the
output correctly.
So there we are. We now have a suite of tests to
give us more confidence that our code is production
ready – as long as the tests don’t fail in our pipeline,
we can be confident that we have met the core
criteria for our GetApiData function.

CONCLUSION
There’s a lot to these functions and it can take some
time to get used to writing this much code: it’s more
code than our actual function!
But what is the price of confidence? If you think
about it, by spending the time writing this code, we
could have saved our company hundreds of
thousands of pounds from lost income if it was
broken in production! Thoroughly testing your code
is an important step – along with static typing,
quality checking and pre-release validation – in
ensuring your code is indeed production ready.
Free download pdf