Creating multiple specs and expectations

This time, we'll create and test the collection utility module. Create the CollectionUtils.js file in the ~/snapterest/source/utils/ directory:

function getNumberOfTweetsInCollection(collection) {
  var TweetUtils = require('./TweetUtils'),
  var listOfCollectionTweetIds = TweetUtils.getListOfTweetIds(collection);

  return listOfCollectionTweetIds.length;
}

function isEmptyCollection(collection) {
  return (getNumberOfTweetsInCollection(collection) === 0);
}

module.exports = {
  getNumberOfTweetsInCollection: getNumberOfTweetsInCollection,
  isEmptyCollection: isEmptyCollection
};

The CollectionUtils module has two methods: getNumberOfTweetsInCollection() and isEmptyCollection().

First, let's discuss getNumberOfTweetsInCollection():

function getNumberOfTweetsInCollection(collection) {
  var TweetUtils = require('./TweetUtils'),
  var listOfCollectionTweetIds = TweetUtils.getListOfTweetIds(collection);

  return listOfCollectionTweetIds.length;
}

As you can see, this function requires the TweetUtils module as a dependency. It then calls the getListOfTweetIds() method and passes the collection object as a parameter. The result returned by getListOfTweetIds() is stored in the listOfCollectionTweetIds variable, and since it's an array, getNumberOfTweetsInCollection() returns a length property of that array.

Now, let's take a look at the isEmptyCollection() method:

function isEmptyCollection(collection) {
  return (getNumberOfTweetsInCollection(collection) === 0);
}

It reuses the getNumberOfTweetsInCollection() method that we just discussed. It checks whether the result returned by a call to getNumberOfTweetsInCollection() is equal to zero. Then, it returns the result of that check, which is either true or false.

Finally, we export both the methods from this module:

module.exports = {
  getNumberOfTweetsInCollection: getNumberOfTweetsInCollection,
  isEmptyCollection: isEmptyCollection
};

We just created our CollectionUtils module. Our next task is to test it.

Inside the ~/snapterest/source/utils/__tests__/ directory, create the following CollectionUtils-test.js file:

jest.autoMockOff();

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

  var CollectionUtils = require('../CollectionUtils'),

  var collectionTweetsMock = {
    collectionTweet7: {},
    collectionTweet8: {},
    collectionTweet9: {}
  };

  it('returns a number of tweets in collection', function getNumberOfTweetsInCollection() {

    var actualNumberOfTweetsInCollection = CollectionUtils.getNumberOfTweetsInCollection(collectionTweetsMock);
    var expectedNumberOfTweetsInCollection = 3;

    expect(actualNumberOfTweetsInCollection).toBe(expectedNumberOfTweetsInCollection);

  });

  it('checks if collection is not empty', function isNotEmptyCollection() {

    var actualIsEmptyCollectionValue = CollectionUtils.isEmptyCollection(collectionTweetsMock);

    expect(actualIsEmptyCollectionValue).toBeDefined();
    expect(actualIsEmptyCollectionValue).toBe(false);
    expect(actualIsEmptyCollectionValue).not.toBe(true);

  });

});

The CollectionUtils-test.js is a bigger test suit than the one we created earlier. There are quite a few things to learn from it. Let's discuss it in detail.

The very first thing that we need to do is to tell Jest not to mock automatically:

jest.autoMockOff();

What does it mean to mock automatically? In the testing environment, Jest replaces your usual require() function with its own version. Jest's version of require() function loads the real module and replaces that module with its mock version. As a result, none of the require() function calls will work as expected in our testing environment. Not even the ones that are used to import the dependency modules inside a module that we're testing. This means that for the module that we want the test to work, we need to call jest.dontMock() for that module, and then for each module that it depends on. There can be a large number of dependency modules. So instead of calling jest.dontMock() too many times, we can reverse the situation by calling the jest.autoMockOff() method. Now Jest won't mock by default, which means that we will need to explicitly call the jest.mock() method for each module that we want to mock.

In CollectionUtils-test.js, we only import the two modules: CollectionUtils and its dependency module TweetUtils. So a less efficient alternative to jest.autoMockOff() would be as follows:

jest.dontMock('../CollectionUtils'),
jest.dontMock('../TweetUtils'),

Once you call jest.autoMockOff() to turn the automatic mocking off, you can then call jest.autoMockOn()to turn it back on later in your code.

After taking care of turning off the automatic mocking, we define our test suit:

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

  var CollectionUtils = require('../CollectionUtils'),

  var collectionTweetsMock = {
    collectionTweet7: {},
    collectionTweet8: {},
    collectionTweet9: {}
  };

  // Specs go here...
});

We give it a name Collection utilities module, since it describes exactly what module we're testing. Now let's take a look at the implementation of this test suit. Instead of immediately defining specs like we did in our previous test suit, we're importing the CollectionUtils module and creating the collectionTweetsMock object. So, are we allowed to do that? Absolutely. The test suite implementation function is just another JavaScript function, where we can do some work before we define our test specs.

This test suit will implement more than one spec. All of them will use the collectionTweetsMock object, so it makes sense to define it outside the specs' scope and reuse it inside the specs. As you might have already guessed, the collectionTweetsMock object imitates a collection of tweets.

Now let's implement the individual specs.

Our first spec tests whether the CollectionUtils module returns a number of tweets in the collection:

it('returns a number of tweets in collection', function getNumberOfTweetsInCollection() {

  var actualNumberOfTweetsInCollection = CollectionUtils.getNumberOfTweetsInCollection(collectionTweetsMock);
  var expectedNumberOfTweetsInCollection = 3;

  expect(actualNumberOfTweetsInCollection).toBe(expectedNumberOfTweetsInCollection);

});

We first get the actual number of tweets in our mock collection:

var actualNumberOfTweetsInCollection = CollectionUtils.getNumberOfTweetsInCollection(collectionTweetsMock);

For this, we call the getNumberOfTweetsInCollection() method and pass the collectionTweetsMock object to it. Then, we define the number of expected tweets in our mock collection:

var expectedNumberOfTweetsInCollection = 3;

Finally, we call the expect() global function to create an expectation:

expect(actualNumberOfTweetsInCollection).toBe(expectedNumberOfTweetsInCollection);

We use the toBe() matcher function to match the actual value and the expected one.

If you now run the npm test command, you will see that both the test suits pass:

Using Jest CLI v0.4.18
 PASS  source/utils/__tests__/TweetUtils-test.js (0.094s)
 PASS  source/utils/__tests__/CollectionUtils-test.js (0.104s)
2 tests passed (2 total)
Run time: 1.297s

Remember that for a test suit to pass, it must have only the passing specs. For a spec to be passing, it must have all its expectations to be met. That's the case so far.

How about running a little evil experiment?

Open your ~/snapterest/source/utils/CollectionUtils.js file, and inside the getNumberOfTweetsInCollection() function, go to the following line of code:

return listOfCollectionTweetIds.length;

Now change it to this:

return listOfCollectionTweetIds.length + 1;

What this tiny update will do is return an incorrect number of tweets in any given collection. Now run npm test one more time. You should see that all your specs in CollectionUtils-test.js have failed. Here is the one we're interested in:

FAIL  source/utils/__tests__/CollectionUtils-test.js (0.12s)
Collection utilities module › it returns a number of tweets in collection
  - Expected: 4 toBe: 3
        at Spec.getNumberOfTweetsInCollection (/Users/artemij/snapterest/source/utils/__tests__/CollectionUtils-test.js:18:46)
        at Timer.listOnTimeout [as ontimeout] (timers.js:112:15)
...

We haven't seen a failing test before, so let's take a closer look at what it's trying to tell us.

First, it gives us the bad news that the CollectionUtils-test.js test has failed:

FAIL  source/utils/__tests__/CollectionUtils-test.js (0.12s)

Then, it tells us in a human-friendly manner what we were testing:

Collection utilities module › it returns a number of tweets in collection

Then, what went wrong; the unexpected test result:

- Expected: 4 toBe: 3

Finally, Jest prints a stack trace that should give us enough technical details to quickly identify what part of our code has produced the unexpected result:

at Spec.getNumberOfTweetsInCollection (/Users/artemij/snapterest/source/utils/__tests__/CollectionUtils-test.js:18:46)
at Timer.listOnTimeout [as ontimeout] (timers.js:112:15)

Notice that it says at which spec an expectation wasn't met:

at Spec.getNumberOfTweetsInCollection

The getNumberOfTweetsInCollection function is the name of the function that implements that spec:

it('returns a number of tweets in collection', function getNumberOfTweetsInCollection() {

  // Spec implementation goes here...

});

Choosing to name the function that implements your spec proves to be very helpful in the case where it fails.

Alright! Enough of failing our tests on purpose. Let's revert our ~/snapterest/source/utils/CollectionUtils.js file as follows:

return listOfCollectionTweetIds.length;

The distinct difference between our previous test suit and CollectionUtils-test.js is the number of specs. A test suit in Jest can have many specs that test different methods of a single module.

Our CollectionUtils module has two methods. Our test suit for it will have three specs. Let's discuss the other two.

Our next spec in CollectionUtils-test.js checks whether the collection is not empty:

it('checks if collection is not empty', function isNotEmptyCollection() {

  var actualIsEmptyCollectionValue = CollectionUtils.isEmptyCollection(collectionTweetsMock);

  expect(actualIsEmptyCollectionValue).toBeDefined();
  expect(actualIsEmptyCollectionValue).toBe(false);
  expect(actualIsEmptyCollectionValue).not.toBe(true);

});

First, we call the isEmptyCollection() method and pass the collectionTweetsMock object to it. We store the result in the actualIsEmptyCollectionValue variable. Notice how we're reusing the same collectionTweetsMock object, as in our previous spec.

Next, we create not one but three expectations:

expect(actualIsEmptyCollectionValue).toBeDefined();
expect(actualIsEmptyCollectionValue).toBe(false);
expect(actualIsEmptyCollectionValue).not.toBe(true);

You might have already guessed what we're expecting from our actualIsEmptyCollectionValue:

We expect it to be defined as follows:

expect(actualIsEmptyCollectionValue).toBeDefined();

This means that the isEmptyCollection() function must return something other than undefined.

We expect its value to be false:

expect(actualIsEmptyCollectionValue).toBe(false);

Earlier, we used the toEqual() matcher function to compare the arrays. toEqual() does a deep comparison, which is perfect for comparing arrays, but it is an overkill for primitive values, such as false.

Finally, we expect actualIsEmptyCollectionValue not to be true:

expect(actualIsEmptyCollectionValue).not.toBe(true);

The .not inverses the next comparison. It matches the expectation with the inverse of toBe(true) with false.

Notice that toBe(false) and not.toBe(true) produce the same result.

Only when all the three expectations are met, then this spec is passing.

So far, we've tested the utility modules, but how do you test the React components with Jest? Let's find this out next.

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

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