6 min read

In this article by Artemij Fedosejev, the author of React.js Essentials, we will take a look at test suites, specs, and expectations.

To write a test for JavaScript functions, you need a testing framework. Fortunately, Facebook built their own unit test framework for JavaScript called Jest. It is built on top of Jasmine – another well-known JavaScript test framework. If you’re familiar with Jasmine you’ll find Jest’s approach to testing very similar. However I’ll make no assumptions about your prior experience with testing frameworks and discuss the basics first.

The fundamental idea of unit testing is that you test only one piece of functionality in your application that usually is implemented by one function. And you test it in isolation – meaning that all other parts of your application which that function depends on are not used by your tests. Instead, they are imitated by your tests. To imitate a JavaScript object is to create a fake one that simulates the behavior of the real object. In unit testing the fake object is called mock and the process of creating it is called mocking.

Jest automatically mocks dependencies when you’re running your tests. Better yet, it automatically finds tests to execute in your repository. Let’s take a look at the example.

Create a directory called ./snapterest/source/js/utils/ and create a new file called TweetUtils.js within it, with the following contents:

function getListOfTweetIds(tweets) {
  return Object.keys(tweets);
}

module.exports.getListOfTweetIds = getListOfTweetIds;

TweetUtils.js file is a module with the getListOfTweetIds() utility function for our application to use. Given an object with tweets, getListOfTweetIds() returns an array of tweet IDs. Using the CommonJS module pattern we export this function:

module.exports.getListOfTweetIds = getListOfTweetIds;

Jest Unit Testing

Now let’s write our first unit test with Jest. We’ll be testing our getListOfTweetIds() function.

Create a new directory: ./snapterest/source/js/utils/__tests__/. Jest will run any tests in any __tests__ directories that it finds within your project structure. So it’s important to name your directories with tests: __tests__.

Create a TweetUtils-test.js file inside of __tests__:
jest.dontMock('../TweetUtils');

describe('Tweet utilities module', function () {

  it('returns an array of tweet ids', function () {

    var TweetUtils = require('../TweetUtils');
    var tweetsMock = {
      tweet1: {},
      tweet2: {},
      tweet3: {}
    };
    var expectedListOfTweetIds = ['tweet1', 'tweet2', 'tweet3'];
    var actualListOfTweetIds = TweetUtils.getListOfTweetIds(tweetsMock);

    expect(actualListOfTweetIds).toBe(expectedListOfTweetIds);

  });

});

First we tell Jest not to mock our TweetUtils module:

jest.dontMock('../TweetUtils');

We do this because Jest will automatically mock modules returned by the require() function. In our test we’re requiring the TweetUtils module:

var TweetUtils = require('../TweetUtils');

Without the jest.dontMock(‘../TweetUtils’) call, Jest would return an imitation of our TweetUtils module, instead of the real one. But in this case we actually need the real TweetUtils module, because that’s what we’re testing.

Creating test suites

Next we call a global Jest function describe(). In our TweetUtils-test.js file we’re not just creating a single test, instead we’re creating a suite of tests. A suite is a collection of tests that collectively test a bigger unit of functionality. For example a suite can have multiple tests which tests all individual parts of a larger module. In our example, we have a TweetUtils module with a number of utility functions. In that situation we would create a suite for the TweetUtils module and then create tests for each individual utility function, like getListOfTweetIds().

describe defines a suite and takes two parameters:

  • Suite name – the description of what is being tested: ‘Tweet utilities module’.
  • Suit implementation: the function that implements this suite.

In our example, the suite is:

describe('Tweet utilities module', function () {
  // Suite implementation goes here...
});

Defining specs

How do you create an individual test? In Jest, individual tests are called specs. They are defined by calling another global Jest function it(). Just like describe(), it() takes two parameters:

  • Spec name: the title that describes what is being tested by this spec: ‘returns an array of tweet ids’.
  • Spec implementation: the function that implements this spec.

In our example, the spec is:

it('returns an array of tweet ids', function () {
  // Spec implementation goes here...
});

Let’s take a closer look at the implementation of our spec:

var TweetUtils = require('../TweetUtils');
var tweetsMock = {
  tweet1: {},
  tweet2: {},
  tweet3: {}
};
var expectedListOfTweetIds = ['tweet1', 'tweet2', 'tweet3'];
var actualListOfTweetIds = TweetUtils.getListOfTweetIds(tweetsMock);

expect(actualListOfTweetIds).toEqual(expectedListOfTweetIds);

This spec tests whether getListOfTweetIds() method of our TweetUtils module returns an array of tweet IDs when given an object with tweets.

First we import the TweetUtils module:

var TweetUtils = require('../TweetUtils');

Then we create a mock object that simulates the real tweets object:

var tweetsMock = {
  tweet1: {},
  tweet2: {},
  tweet3: {}
};

The only requirement for this mock object is to have tweet IDs as object keys. The values are not important hence we choose empty objects. Key names are not important either, so we can name them tweet1, tweet2 and tweet3. This mock object doesn’t fully simulate the real tweet object. Its sole purpose is to simulate the fact that its keys are tweet IDs.

The next step is to create an expected list of tweet IDs:

var expectedListOfTweetIds = ['tweet1', 'tweet2', 'tweet3'];

We know what tweet IDs to expect because we’ve mocked a tweets object with the same IDs.

The next step is to extract the actual tweet IDs from our mocked tweets object. For that we use getListOfTweetIds()that takes the tweets object and returns an array of tweet IDs:

var actualListOfTweetIds = TweetUtils.getListOfTweetIds(tweetsMock);

We pass tweetsMock to that method and store the results in actualListOfTweetIds. The reason this variable is named actualListOfTweetIds is because this list of tweet IDs is produced by the actual getListOfTweetIds() function that we’re testing.

Setting Expectations

The final step will introduce us to a new important concept:

expect(actualListOfTweetIds).toEqual(expectedListOfTweetIds);

Let’s think about the process of testing. We need to take an actual value produced by the method that we’re testing – getListOfTweetIds(), and match it to the expected value that we know in advance. The result of that match will determine if our test has passed or failed.

The reason why we can guess what getListOfTweetIds() will return in advance is because we’ve prepared the input for it – that’s our mock object:

var tweetsMock = {
  tweet1: {},
  tweet2: {},
  tweet3: {}
};

So we can expect the following output from calling TweetUtils.getListOfTweetIds(tweetsMock):

['tweet1', 'tweet2', 'tweet3']

But because something can go wrong inside of getListOfTweetIds() we cannot guarantee this result – we can only expect it.

That’s why we need to create an expectation. In Jest, an Expectation is built using expect()which takes an actual value, for example: actualListOfTweetIds.

expect(actualListOfTweetIds)

Then we chain it with a Matcher function that compares the actual value with the expected value and tells Jest whether the expectation was met.

expect(actualListOfTweetIds).toEqual(expectedListOfTweetIds);

In our example we use the toEqual() matcher function to compare two arrays.

Click here for a list of all built-in matcher functions in Jest.

And that’s how you create a spec. A spec contains one or more expectations. Each expectation tests the state of your code. A spec can be either a passing spec or a failing spec. A spec is a passing spec only when all expectations are met, otherwise it’s a failing spec.

Well done, you’ve written your first testing suite with a single spec that has one expectation.

Continue reading React.js Essentials to continue your journey into testing.

LEAVE A REPLY

Please enter your comment!
Please enter your name here