javascript
testing
beginners
codenewbie
Published: Mon Apr 12 2021 (~ 7 min)
Read on Dev.toUnit testing is an integral part of Test-Driven Development (TDD) which is the process of defining the desired actions of a function and what we expect it to do (or not do) before we begin work on the actual function. Approaching software development in this fashion serves a number of purposes:
Programming languages have their own frameworks for developing unit tests. For Javascript, Jest is one of the most widely used testing frameworks, and I hope this blog serves as a beginner's guide for those looking to get started in writing their own Jest tests.
We will walk through the process of setting up basic Jest tests and the files, but you can view the repo containing all of the code here
Steps:
cd
into that directory.[object Object]
[object Object]
package.json
file created earlier. This edit will cause the command npm test
to run the tests we will be building.[object Object]
To begin writing the tests, we must define what the function we will be building should do, and what the expected outcome should be when the function is invoked.
For our example, let's consider an object containing information about a user's blog posts:
[object Object]
We will be writing two functions,
getTotalLikes
to get the total number of likes of the given user's posts,getMostPopularBlog
to return the blog object of a specified user with the most likes.Following the TDD process, we will develop tests for these functions prior to working out the logic for the functions themselves.
Typically, tests are written in a tests
or __tests__
subdirectory of the app, and we will follow this same convention. From the root of our example project, let's create a tests
directory and the file which will contain our tests.
[object Object]
The first thing we must do in this new file is to import the functions that we will be testing (it's ok that they have not yet been written.) For the sake of this blog, we will be writing both of the sample functions into the same .js
file, and we will use destructuring in the import to get access to both of those functions.
[object Object]
Both of the example functions discussed above will be tested using the same sample user
object mentioned previously, so we can define this globally for our tests file as well.
[object Object]
Tests typically contain these general components:
describe
function is invoked which accepts two arguments:
test
function which accepts two arguments:
expect
function and a matcher
function.
expect
function accepts the function invocation being tested, and is chained to the matcher
which describes the expected results.In the getTotalLikes
function, we expect that when the function is passed a user object, the return value will be an integer that is the sum of the likes
on all of the blogs of that user. Including this into our test file would look like this:
[object Object]
Here, the .toBe
matcher is used to define the expected output of the function invocation written in the preceeding expect
statement. The .toBe
matcher returns truthy if the output of the function is equal to the value passed into the matcher. The Jest framework has a number of defined matchers, such as:
toBeNull
matches only nulltoBeUndefined
matches only undefinedtoBeDefined
is the opposite of toBeUndefinedtoBeTruthy
matches anything that an if statement treats as truetoBeFalsy
matches anything that an if statement treats as falsetoBeGreaterThan
or toBeLessThan
for number value comparisonstoMatch
accepts a Regex pattern to match a string outputtoContain
can be used to see if a value is contained in an ArrayMore common Jest Matchers can be found in the official introduction here or a complete list can be found in the official docs here
For our second function, we can define the expected output object within the describe
block's scope and pass this object into our matcher. Doing this, we will again be checking for equality; however when dealing with objects, we must use .toEqual
instead, which iterates through all of the values of the objects to check for equality.
With this in mind, we must add this final describe
block to our test file:
[object Object]
The tests we have written should clearly fail because we have not yet written the functions; however, we can run the test to ensure that they are properly set up.
To run the tests, run npm test
(which matches the command we defined in the package.json
). We are wonderfully greeted with the expected failures that our functions are not defined, and it indicates that our test file is prepared.
[object Object]
Create a new file in /jest-example
which will contain our functions. The name of the file should match the filename of the test file, minus the .test
extension.
In /jest-example
[object Object]
In this file we need to define out two functions, and ensure that we export those functions so that our test file can access them.
[object Object]
If we save and run our tests again, we will see that all four tests still fail (which is expected), but Jest provides a ne message to us indicating what happened.
[object Object]
This message indicates that our test is able to find the matching function, unlike before, but now instead of getting the expected value that was passed to the matcher
, no value is being returned from our function. Let's implement the logic for our two functions as shown below:
[object Object]
Now, if we run the tests one final time, we are greeted with pass indicators:
[object Object]
Testing is powerful. Even with these limited tests, we would would be able to see if changes further along in the development process negatively impact the work we have already done. For example, if the structure of the API response that we used to build the user
object changed, running the test file would indicate an issue prior to that change going into effect. This is especially important in development teams, where multiple developers are working on the same codebase. The tests help ensure that new code remains compatible and functional with the codebase and with that of other developers.
However, the reliability and power of testing is limited by the comprehensiveness of the test scenarios. As you are building tests, remember to consider the edge case scenarios that could break the function of your application, and write tests to simulate those. For example:
The topic of testing goes very deep, but hopefully this helps you get started with understanding the testing process and developing your own tests.