Fundamentals Of Testing
This section will create simple versions of (1) a testing framework and (2) an assertion library. The goal is to understand how these work by building our own.
A Test Throws an Error
At its core, a test is code that throws an error when the actual result doesn't match the expected result.
This can get complicated when dealing with code with multiple layers of logic because there are several branching steps involved. However, a simple bit of code to test is pure functions: functions that always return the same output given the same input--and which don't mutate anything around them.
In the console, this should show an error message.
Abstracting into Assertions
To make our code more reusable, we can encapsulate and abstract our logic checks into assertion functions under expect
:
Encapsulating Tests using a Testing Framework
There are 2 problems with using expect
on its own:
If we have multiple tests and one fails, the ones after never get to run. That's because thrown errors halt the code.
When the error is thrown, by default, we're told the line number that the error was thrown. However, we aren't given precise information about what went wrong. If we have multiple tests, we won't know which one broke.
To solve these 2 problems, we introduce a testing framework. We will implement a test
function, which solves the 2 problems in the following way:
Using a
try/catch
, we can catch an error and display it without halting the other tests.Inside our
try
, we can state something about the test that was successful, and inside ourcatch
, we can state something about the test that failed.
Supporting Async Tests
Suppose we're testing a sumAsync
function that rejects.
This is a problem for our current code because the rejection never gets caught. Instead, we receive a false positive saying SUCCESS: sumAsync adds 1 and 2 to make 3
followed by an unhandled promise rejection error.
Think about what happens inside test
to cause this:
callback
gets called.Inside
callback
,sumAsync
rejects.This exits
callback
, which returns a rejected promise itself (becauseasync
functions return promises, and the rejection travels up the call stack).However, JavaScript doesn't do anything with the rejected promise. It's unhandled. This leads to the rest of the
try
block running, leading to a false positive.
The solution to this problem is to convert test
itself into a function that uses async/await
. This solution works because await
will throw the rejected value of the promise to the catch
block.
Making Testing Global
Many testing frameworks and assertion libraries automatically make their helper functions globally available. To do this yourself, simply create a setup-globals.js
:
All you're doing is attaching your helpers to the global
object in node. Then you can run the file prior to running any tests.
For example: node --require setup-globals.js test.js
.
Jest
Everything we've done so far is basically like jest. What makes jest special, however, is the fact that their error messages are very clear.
Last updated