cft

Basics of Javascript Test Driven Development with Jest

Test Driven Development (TDD)'s main idea is to simply start working on code by writing automated tests BEFORE writing the code that is being tested. There are many test-running systems in Javascript: Jasmine, Jest, Tape, and Mocha to name a few. They have their special features but the syntax is very similar. The framework chosen should not be an issue because writing tests is less about the syntax but more on the TDD philosophy. This article is all about the basics of Javascript TDD using Jest


user

Patricia Nicole Opetina

2 years ago | 5 min read

Test Driven Development (TDD)'s main idea is to simply start working on code by writing automated tests **BEFORE** writing the code that is being tested. There are many test-running systems in Javascript: Jasmine, Jest, Tape, and Mocha to name a few. They have their special features but the syntax is very similar. The framework chosen should not be an issue because

writing tests is less about the syntax but more on the TDD philosophy

so I tried internalizing the concepts using Jest. My main goal while doing the exercise is to know the why's and whats of testing.

Before diving in, here are some notes I took from this brilliant talk, The Magic of Testing

  1. Why does most devs hate tests? Because they are slow and fragile and expensive (time).
  2. It is perfectly valid to delete some tests.
  3. Unit Test Goals: They must be thorough (we want them to prove logically and completely that the single object under test is behaving correctly) and stable (we dont want to break the test everytime the implementation detail is changed 😟), fast and few (write tests for the most parsimonious expression [mmmmmm 🤔]).
  4. Do not test private methods. But break this rule if it saves money during development.
  5. A mock is a test double, it plays the role of some object in your real app. Ensure test double stays in sync with the API.
  6. Trust collaborators that they will do the right thing. Insist on simplicity.
  7. Getting better at testing takes time and practice.

The object under test have three origin of messages :

  • Incoming - messages to the object from outside
  • Self - messages sent by the object under test to itself
  • Outgoing - messsages sent by the object to the outside.


There are two types of messages : query and command. Queries return something or changes nothing. Command types return nothing but changes something.



Grid of Test Rules

The grid of test results shows how each type of message can be unit tested

Grid of Test Results
Grid of Test Results

Advantages of TDD

  1. Reduces bugs that may be introduced when adding new features or modifying existing features
  2. Builds a safety net against changes of other programmers that may affect a specific part of the code
  3. Reduces the cost of change by ensuring that the code will still work with the new changes
  4. Reduces the need for manual (monkey) checking by testers and developers
  5. Improves confidence in code
  6. Reduces the fear of breaking changes during refactors

Getting Started with Jest

Jest is a javascript testing framework focusing on simplicity but still ensures the correctness of the Javascript code base. It boasts itself to be fast and safe, reliably running tests in parallel with unique global state. To make things quick, Jest runs previously failed tests first and re-organizes runs based on how long test files take.

Moreover, Jest is very well-documented and requires little configuration. It indeed makes javascript testing delightful. It can be installed by using either `yarn` or `npm`.

Three Modes of TDD

  1. Obvious implementation. You write the test with the implementation since you know how to implement the method to test.
  2. Fake it til you make it. If you know the problem and solutions, but the way you code them up is not immediately obvious to you, then you can use a trick called "fake it 'til you make it."
  3. Triangulation . This is the most conservative way of doing TDD. If you don't even know the solution, you just get to green at all costs , red-green, red-green loop.

Using Jest Matchers

Common Matchers

The simplest way to test a value is with exact equality.

test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});

The code snippet above returns an "expectation" object. The `toBe(3)` portion is the matcher. When Jest runs, it tracks all the failing matchers so it can print nice error messages. The `toBe` matcher uses `Object.is` to test the equality.

Truthiness

In unit tests, the special values `undefined`, `null`, `false` might be needed to be checked also. Jest contains helpers that lets developers be explicit with what to expect. It is then good to use a matcher that most precisely corresponds to what the code is doing.

  • `toBeNull` matches only `null`
  • `toBeUndefined` matches only `undefined`
  • `toBeDefined` is the opposite of `toBeUndefined`
  • `toBeTruthy` matches anything that an `if` statement treats as true
  • `toBeFalsy` matches anything that an `if` statement treats as false

Numbers

There are also Jest matchers for comparing numbers such as `toBeGreaterThan`, `toBeGreaterThanOrEqual`, `toBeLessThan`, `toBeLessThanOrEqual`. For floating point numbers, there are equality matcher like `toBeCloseTo`.

Strings

Strings can be checked against regular expressions using `toMatch`.

Arrays and Iterables

`toContain` can be used to check if a particular item can be found in an array or iterable.

Exceptions

`toThrow` can be used to check if a particular function throws a specific error. It is to be noted that the function being checked needs to be invoked within a wrapping function for the `toThrow` exception to work.

There are also more advanced Jest matchers used for testing asynchronous code, i.e for callbacks and promises.


Jest Testing Practice

This is my first time writing javascript unit tests using Jest. It is quite new so I needed some practice :smile:. I tried using the **obvious implementation** and **triangulation** mode of testing for some of the methods below. The full implementation of the methods and their corresponding tests can be found in my [Jest practice github repository](https://github.com/fatrixienicolieopetina/practice-jest-testing/tree/main/_js).

  • `capitalize(string)` takes a string and returns that string with the first character capitalized.

`capitalize.test.js`

const capitalize = require('../capitalize');
test('should capitalize lowercase string correctly', () => {
expect(capitalize("capitalize")).toBe("Capitalize");
});
test("should return '' for strings with length 0", () => {
expect(capitalize("")).toBe("");
});
// other tests here

  • `reverseString(string)` takes a string and returns it reversed. Below is a snippet of the test I wrote for a normal scenario.

`reverse-string-test.js`

const reverseString = require('../reverse-string');
test('should reverse normal strings', () => {
expect(reverseString("reverse")).toBe("esrever");
});
//other tests here

  • A `calculator` object that contains the basic operations: `add`, `subtract`, `divide`, and `multiply`. The following test snippet below shows that the method will throw an error message if the divisor is zero.

`calculator.test.js`

const calculator = require("../calculator");
//other tests here
test("should throw an error if divisor is 0", () => {
expect(() => calculator.divide(20, 0)).toThrow("cannot divide by 0");
});

  • `caesar cipher`. A caesar cipher is a substitution cipher where each letter in the text is shifted a certain places number down the alphabet. More info can be read here.

One thing to remember from this part of the exercise is that it is not needed to explicity test the smaller functions, just the public ones. If the larger function works then it must be the case that the helper methods are functioning well.

`caesar-cipher.test.js`

const caesar = require("../caesar-cipher");
//other tests here
test('wraps', function() {
expect(caesar('Z', 1)).toEqual('A');
});
test('works with large shift factors', function() {
expect(caesar('Hello, World!', 75)).toEqual('Ebiil, Tloia!');
});
test('works with large negative shift factors', function() {
expect(caesar('Hello, World!', -29)).toEqual('Ebiil, Tloia!');
});

  • Array Analysis. This function takes an array of numbers and returns an object with the following properties: `average`, `min`, `max`, and `length`.

`analyze.test.js`

const analyze = require("../analyze");
const object = analyze([1,8,3,4,2,6]);
test("should return correct average", () => {
expect(object.average).toEqual(4);
});
test("should return correct min", () => {
expect(object.min).toEqual(1);
});
// other tests here

Check out the github repository of the included snippets in here for a complete picture of the tests.

The concepts and points above are the very basics of TDD using Jest. There are a lot more to learn, from more advanced matchers, mocking, testing asynchronous parts of the code, and others. I am still to learn them and that is for another dev post :p

Cheers to continued learning!

REFERENCES

  1. The Importance of TDD
  2. TOP Testing Basics
  3. Jest Getting Started Documentation
  4. Jest Official Docs

Upvote


user
Created by

Patricia Nicole Opetina

i am just here


people
Post

Upvote

Downvote

Comment

Bookmark

Share


Related Articles