Creating test suits, specs, and expectations

How does one write a test for JavaScript functions? You need a testing framework, and luckily, Facebook has built its own unit test framework for JavaScript called Jest. It is built on top of Jasmine; another well-known JavaScript test framework. Those of you who are familiar with Jasmine will 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. You test it in isolation, which means that all the other parts of your application that the 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 the dependencies when you're running your tests. It automatically finds tests to be executed in your repository. Let's take a look at the following example.

First, create the ~/snapterest/source/utils/ directory. Then, create a new TweetUtils.js file in it:

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

module.exports.getListOfTweetIds = getListOfTweetIds;

The 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 will export this function:

module.exports.getListOfTweetIds = getListOfTweetIds;

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

Create a new directory: ~/snapterest/source/utils/__tests__/. Jest will run any tests in any __tests__ directories that it will find in your project structure. So, it's important to name your directories with __tests__.

Create a TweetUtils-test.js file inside __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).toEqual(expectedListOfTweetIds);
  });
});

First, we tell Jest not to mock our TweetUtils module:

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

We need to do this because Jest will automatically mock modules returned by the require() function. In our test, we require 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. In this case, we actually need the real TweetUtils module because that's what we're testing.

Next, we call a global describe()Jest function. It's important to understand the concept behind it. In our TweetUtils-test.js file, we're not just creating a single test, instead we're creating a suit of tests. A suit is a collection of tests that collectively tests a bigger unit of functionality. For example, a suit can have multiple tests, which tests all the individual parts of a larger module. In our example, we have a TweetUtils module with potentially a number of utility functions. In this situation, we would create a suit for the TweetUtils module, and then create tests for each individual utility function, such as getListOfTweetIds().

The describe function defines a suit and takes these two parameters:

  • Suit name: This is the title that describes what is being tested by this 'Tweet utilities module' suit
  • Suit implementation: This is the function that implements this suit

In our example, the suit is as follows:

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

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

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

In our example, the spec is as follows:

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 the getListOfTweetIds() method of our TweetUtils module returns an array of tweet IDs, when given an object with tweets.

First, we will import the TweetUtils module:

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

Then, we will 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, so we need to choose empty objects. The key names are not important as well, so we choose to 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 the tweets object with the same IDs.

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

var actualListOfTweetIds = TweetUtils.getListOfTweetIds(tweetsMock);

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

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, that is, getListOfTweetIds(), and match it to the expected value that we know in advance. The result of that match will determine whether 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 by calling TweetUtils.getListOfTweetIds(tweetsMock):

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

Because something can go wrong inside 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 the expect() function, which takes an actual value, for example, actualListOfTweetIds object: 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 or not:

expect(actualListOfTweetIds).toEqual(expectedListOfTweetIds);

In our example, we use the toEqual() matcher function to compare the two arrays. You can find a list of all the built-in matcher functions in Jest at https://facebook.github.io/jest/docs/api.html#expect-value.

This is 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 the expectations are met, otherwise it's a failing spec.

Well done, you've written your first testing suit with a single spec that has one expectation. How can you run it?

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.137.243