Categories
Testing

How to Test Node.js Apps That Interact With External APIs Using Nock

Today’s web apps rarely live alone. They often make requests to external APIs via HTTP requests.

Fortunately, testing these kinds of code isn’t too hard since we can mock the HTTP responses. This means that our tests don’t have to make the requests in our tests, which is critical for writing repeatable and independent tests.

In this piece, we’ll look at how to test an Express app that interacts with an external API.


Creating a Simple App

First, we create the app that we’ll test. The app will get a random joke from the Chuck Norris Jokes API.

Next, we create an empty project folder and run:

npm init -y

To create an empty package.json file with the default answers.

Next, we install Express and Node-Fetch to create the app and get data from APIs, respectively.

We run:

npm i express node-fetch

To install the packages.

Then we create app.js and add:

To make the app that gets the joke and returns it as the response.


Adding the Test

To add the test, we use the Jest test runner to run the test and check the results, Nock to mock the response for the request that we made in app.js, and Supertest to run the Express app and get the response that we can check.

To do this, we run:

npm i nock supertest jest

Then we create app.test.js for the test code and then add:

In the file.

The code above starts by including our app from app.js and the libraries we use for building the test.

const app = require('./app');  
const nock = require('nock');  
const request = require('supertest');

The first line above imports the Express app instance from app.js. Then the other two lines import the Nock and Supertest libraries, respectively.

Then we add the skeleton for the test by calling test from Jest and pass in the description and async function since we’ll use the promise version of Supertest.

Then using Nock, we mock the response as follows:

As we can see, the mock request is a GET request, which is the same as what we had in app.js. Also, the URL is the same as what we had there.

The difference is that we return a 200 response with the mockResponse object in the test.

Now it doesn’t matter what the actual API returns. Nock will intercept the request and always return the content of mockResponse as the response body for the request made in our route in app.js.

Then we just have to call the route with Supertest as follows:

const res = await request(app).get('/');

To call the route in app.js and then check the response as follows:

expect(res.status).toBe(200);  
expect(res.body).toEqual(mockResponse);

The toEqual method checks for deep equality so we can pass in the whole object to check.

Photo by Filip Mroz on Unsplash.


Running the Test

Now to run our test, we have to add:

"test": "jest --forceExit"

To the scripts section of package.json.

We need the --forceExit option so that the test will exit once the test is run. This is a bug that’s yet to be resolved.

Now we can run the test by running:

npm test

Then we should get:

We should get the same thing no matter how many times we run the test since we mocked the response of the API.

The real API returns something different every time we make a request to it, so we can’t use it for our tests.

Even if we could, it’d be much slower and the API might not always be available.


Conclusion

With Nock, we can mock responses easily for external API requests in our app. Then we can focus on just running our app’s code and checking the results.

Now we have tests that run fast and produce repeatable results that don’t depend on anything external to the tests.

Categories
Testing

Jasmine — Timer, and Async Tests

Testing is an important part of JavaScript.

In this article, we’ll look at how to create more complex tests with Jasmine.

Testing JavaScript Timeout Functions

We can use Jasmine to test JavaScript timeout functions.

We can use the jasmine.clock() method to do this.

For instance, we can write:

describe("Manually ticking the Jasmine Clock", function () {
  let timerCallback;

  beforeEach(function () {
    timerCallback = jasmine.createSpy("timerCallback");
    jasmine.clock().install();
  });

  afterEach(function () {
    jasmine.clock().uninstall();
  });

  it("causes a timeout to be called synchronously", function () {
    setTimeout(function () {
      timerCallback();
    }, 100);

    expect(timerCallback).not.toHaveBeenCalled();
    jasmine.clock().tick(101);
    expect(timerCallback).toHaveBeenCalled();
  });
});

to create a timerCallback spy which we can watch.

We call jasmine.clock().install() to create the Jasmine timer.

And we call jasmine.clock().uninstall() to remove it at the end.

Then we call tick to change the time to what we want.

This way, we can check is our setTimeout callback is called.

Also, we can use it to test setInterval .

For instance, we can write:

describe("Manually ticking the Jasmine Clock", function () {
  let timerCallback;

  beforeEach(function () {
    timerCallback = jasmine.createSpy("timerCallback");
    jasmine.clock().install();
  });

  afterEach(function () {
    jasmine.clock().uninstall();
  });

  it("causes a timeout to be called synchronously", function () {
    setInterval(function () {
      timerCallback();
    }, 100);

    expect(timerCallback).not.toHaveBeenCalled();

    jasmine.clock().tick(101);
    expect(timerCallback.calls.count()).toEqual(1);

    jasmine.clock().tick(102);
    expect(timerCallback.calls.count()).toEqual(2);
  });
});

Like with the setTimeout test, we change the clock to the time we want with the tick method.

The time is changed relative to the time that the last tick is called.

Mocking the Date

We can use the mockDate method to mock the date we want.

For instance, we can write:

describe("Manually ticking the Jasmine Clock", function () {
  let timerCallback;

  beforeEach(function () {
    timerCallback = jasmine.createSpy("timerCallback");
    jasmine.clock().install();
  });

  afterEach(function () {
    jasmine.clock().uninstall();
  });

  it("mocks time time", function () {
    const baseTime = new Date(2020, 0, 1);

    jasmine.clock().mockDate(baseTime);

    jasmine.clock().tick(50);
    expect(new Date().getTime()).toEqual(baseTime.getTime() + 50);
  });
});

We et the baseTime to a Date instance and then use that with mockDate to set the date to when we want.

Then we call tick to move the time.

And then we used getTime to get the current time after tick is called.

Async Support

Jasmine supports testing async code.

We can test async code with:

describe("Using callbacks", function () {
  beforeEach(function (done) {
    setTimeout(function () {
      value = 0;
      done();
    }, 1);
  });

  it("supports sequential execution of async code", function (done) {
    value++;
    expect(value).toBeGreaterThan(0);
    done();
  });
});

We have the beforeEach callback that has a setTimeout function call.

We call done in the callback so that the test code is run.

In the test code, the callback takes the done parameter to let us call done to indicate that the test is done.

To fail a test, we can use done.fail to fail the test.

For instance, we can write:

describe("Using callbacks", function () {
  var foo = function (x, callBack1, callBack2) {
    if (x) {
      setTimeout(callBack1, 0);
    } else {
      setTimeout(callBack2, 0);
    }
  };

  it("should not call the second callBack", function (done) {
    foo(true,
      done,
      function () {
        done.fail("Second callback has been called");
      }
    );
  });
});

We called done.fail in the 2nd callback so that if it’s run, the test will fail.

Conclusion

We can run a variety of tests that has timers and async code with Jasmine.

Categories
Testing

Jasmine — Test Array, Strings, and Time-Dependent Code

Testing is an important part of JavaScript.

In this article, we’ll look at how to create more complex tests with Jasmine.

Array Checks

We can use the jasmine.arrayContaining method to check the content of the array.

For example, we can write:

describe("jasmine.arrayContaining", function () {
  let foo;

  beforeEach(function () {
    foo = [1, 2, 3];
  });

  it("matches arrays with some of the values", function () {
    expect(foo).toEqual(jasmine.arrayContaining([3, 1]));
    expect(foo).not.toEqual(jasmine.arrayContaining([6]));
  });

  describe("when used with a spy", function () {
    it("is useful when comparing arguments", function () {
      const callback = jasmine.createSpy('callback');
      callback([1, 2, 3, 4]);
      expect(callback)
        .toHaveBeenCalledWith(
          jasmine.arrayContaining([4, 2, 3])
        );
      expect(callback)
        .not
        .toHaveBeenCalledWith(
          jasmine.arrayContaining([5, 2])
        );
    });
  });
});

We have the jasmine.arrayContaining method to check for the numbers in the array.

It returns true if the array being checked has all the items in the array we passed into arrayContaining .

We can do the same with function arguments.

String Matches

We can use the jasmine.stringMatching to check if a string has a given substring or pattern.

For instance, we can write:

describe('jasmine.stringMatching', function () {
  it("matches as a regex", function () {
    expect({ foo: 'baz' })
      .toEqual({
        foo: jasmine.stringMatching(/^baz$/)
      });
    expect({ foo: 'foobarbaz' })
      .toEqual({
        foo: jasmine.stringMatching('bar')
      });
  });

  describe("when used with a spy", function () {
    it("is useful for comparing arguments", function () {
      const callback = jasmine.createSpy('callback');
      callback('foobarbaz');
      expect(callback)
        .toHaveBeenCalledWith(
          jasmine.stringMatching('baz')
        );
      expect(callback)
        .not
        .toHaveBeenCalledWith(
          jasmine.stringMatching(/^baz$/)
        );
    });
  });
});

We have the jasmine.stringMatching method with a regex and a string.

It lets us check for a regex pattern and a substring.

Asymmetric Equality Tester

We can create our own equality tester to do our check.

For instance, we can write:

describe("custom asymmetry", function () {
  const tester = {
    asymmetricMatch(actual) {
      return actual.includes('bar');
    }
  };

  it("dives in deep", function () {
    expect("foo,bar,baz,quux").toEqual(tester);
  });

  describe("when used with a spy", function () {
    it("is useful for comparing arguments", function () {
      const callback = jasmine.createSpy('callback');
      callback('foo,bar,baz');
      expect(callback).toHaveBeenCalledWith(tester);
    });
  });
});

We created our own tester object with the asymmericMatch method to check for what we want.

We just return a boolean expression with the condition we want to check.

Then we can use it with the toHaveBeenCalledWith or toEqual .

Therefore, we can check for the values and the arguments.

Jasmine Clock

We can test time-dependent code with the jasmine.clock method.

For example, we can write:

describe("custom asymmetry", function () {
  let timerCallback;

  beforeEach(function () {
    timerCallback = jasmine.createSpy("timerCallback");
    jasmine.clock().install();
  });

  afterEach(function () {
    jasmine.clock().uninstall();
  });

  it("causes a timeout to be called synchronously", function () {
    setTimeout(function () {
      timerCallback();
    }, 100);
    expect(timerCallback).not.toHaveBeenCalled();
    jasmine.clock().tick(101);
    expect(timerCallback).toHaveBeenCalled();
  });
});

We have the jasmine.clock().install() to make Jasmine mock the time.

Then we call jasmine.clock().uninstall() to remove the Jasmine clock.

In our test, we can change the time to what we want.

And then we can do our checks after some amount of time is elapsed.

Conclusion

We can test time-dependent code with Jasmine’s clock methods.

Also, we can check for array and string items with built-in functions.

And we can make our own matches to test what we want.

Categories
Testing

Jasmine — Spies and Matchers

Testing is an important part of JavaScript.

In this article, we’ll look at how to create more complex tests with Jasmine.

createSpy

We can call createSpy to stub a function.

They can be used to track arguments.

For instance, we can write:

describe("A spy", function () {
  let spy;

  beforeEach(function () {
    spy = jasmine.createSpy('spy');
    spy('foo', 'bar');
  });

  it("tracks that the spy was called", function () {
    expect(spy).toHaveBeenCalled();
  });
});

We created a spy with jasmine.createSpy .

It returns a function that can be watched with Jasmine.

So we can check if it has been called with toHaveBeenCalled .

createSpyObj

We can use the createSpyObj method to create a mock with multiple spies.

For instance, we can write:

describe("Multiple spies", function () {
  let person;

  beforeEach(function () {
    person = jasmine.createSpyObj('person', ['eat', 'drink', 'sleep', 'walk']);
    person.eat();
    person.drink();
    person.sleep(0);
    person.walk(0);
  });

  it("creates spies for each requested function", function () {
    expect(person.eat).toBeDefined();
    expect(person.drink).toBeDefined();
    expect(person.sleep).toBeDefined();
    expect(person.walk).toBeDefined();
  });
});

We called createSpyObj with the spy name as the first argument.

And the methods available in the spy object in the 2nd argument.

Then we can it and then check if the methods are defined

Matching and Spies

We can add more checks with more matches.

For instance, we can write:

describe("Tests", function () {
  it("matches any value", function () {
    expect({}).toEqual(jasmine.any(Object));
    expect(12).toEqual(jasmine.any(Number));
  });

  describe("when used with a spy", function () {
    it("is useful for comparing arguments", function () {
      const foo = jasmine.createSpy('foo');
      foo(12, () => true);
      expect(foo).toHaveBeenCalledWith(jasmine.any(Number), jasmine.any(Function));
    });
  });
});

We have can use toEqual to check the type of a value with jasmine.any .

We can use jasmine.any with toHaveBeenCalledWith to check if a spy is called with a given type of data.

jasmine.anything

We can use jasmine.anythibng to check if something matches anything that’s not null or undefined .

For instance, we can write:

describe("jasmine.anything", function () {
  it("matches anything", function () {
    expect(1).toEqual(jasmine.anything());
  });

  describe("when used with a spy", function () {
    it("is useful when the argument can be ignored", function () {
      const foo = jasmine.createSpy('foo');
      foo(12, 'bar');
      expect(foo).toHaveBeenCalledWith(12, jasmine.anything());
    });
  });
});

We have jasmine.anything() call to check if anything isn’t null or undefined .

Checking Object Structure

We can check for object structure with the jasmine.objectContaining method.

For instance, we can write:

describe("jasmine.objectContaining", function () {
  let foo;

  beforeEach(function () {
    foo = {
      a: 1,
      b: 2,
      bar: "baz"
    };
  });

  it("matches objects with key-value pairs", function () {
    expect(foo).toEqual(jasmine.objectContaining({
      a: 1, bar: "baz"
    }));
  });

  describe("when used with a spy", function () {
    it("is useful for comparing arguments", function () {
      const callback = jasmine.createSpy('callback');

      callback({
        bar: "baz"
      });

      expect(callback)
        .toHaveBeenCalledWith(
          jasmine.objectContaining({
            bar: "baz"
          })
        );
    });
  });
});

We have the jasmine.objectContaining method to watch for object structure.

We just pass in the key-value pairs we want to check for.

It can be used with values and function arguments.

Conclusion

We can check for values and objects and watch for spies with various Jasmine methods.

Categories
Testing

Jasmine — Async Tests

Testing is an important part of JavaScript.

In this article, we’ll look at how to create more complex tests with Jasmine.

Testing Promises

We can test promises with Jasmine.

The beforeAll , afterAll , beforeEach and afterEach functions all support promises.

For instance, we can write:

describe("Using promises", function () {
  let value;

  beforeEach(function () {
    return Promise.resolve().then(function () {
      value = 0;
    });
  });

  it("should support async execution of tests", function () {
    return Promise.resolve().then(function () {
      value++;
      expect(value).toBeGreaterThan(0);
    });
  });
});

We called then on a promise in the beforeEach and it callbacks.

Then we can check the values in there.

It also works with the async and await syntax.

For instance, we can write:

describe("Using promises", function () {
  let value;

  beforeEach(async function () {
    await Promise.resolve()
    value = 0;
  });

  it("should support async execution of tests", async function () {
    await Promise.resolve()
    value++;
    expect(value).toBeGreaterThan(0);
  });
});

We just rewrote the promises with the async and await syntax.

In both examples, they’ll run sequentially.

The test starts with the beforeEach callback and then run the it callback.

Changing Wait Time

The beforeEach and afterEach functions take an optional 2nd argument to set the wait until done is finished.

For example, we can write:

describe("long asynchronous specs", function () {
  beforeEach(function (done) {
    done();
  }, 1000);

  it("takes a long time", function (done) {
    setTimeout(function () {
      done();
    }, 9000);
  }, 10000);

  afterEach(function (done) {
    done();
  }, 1000);
});

to pass in a 2nd argument with the wait time in milliseconds.

Handling Failures

Handling failures with promises can be done like any other piece of code.

If a promise is rejected in our test, then the test will fail.

For instance, if we have:

describe("promise test", function () {
  it('does a thing', function () {
    return Promise.reject()
  });
});

then the test will fail.

With async and await , we get the same result:

describe("long asynchronous specs", function () {
  it('does a thing', async function () {
    await Promise.reject()
  });
});

This will also fail.

We can fail a test with callbacks.

Since Jasmine 3.0, the done function can detect if an Error object is passed to the function.

If it is, then the test will fail.

For instance, we can write:

describe("long asynchronous specs", function () {
  beforeEach(function (done) {
    setTimeout(function () {
      let err;

      try {
        throw new Error()
      } catch (e) {
        err = e;
      }

      done(err);
    });
  });

  it('does something', () => { })
});

Our beforeEach callback throw an error.

So since we call done with the err object, we’ll see that the test will fail.

Mock Clock

We can use the mock clock to avoid writing async tests.

It’ll make async code run synchronously.

For instance, we can write:

describe("long asynchronous specs", function () {
  beforeEach(function () {
    jasmine.clock().install();
  });

  afterEach(function () {
    jasmine.clock().uninstall();
  });

  it('does something after 10 seconds', function () {
    const callback = jasmine.createSpy('callback');
    setTimeout(function () {
      callback('foo');
    }, 10000);
    jasmine.clock().tick(10000);
    expect(callback).toHaveBeenCalledWith('foo');
  });
});

to create a test with the mock clock.

We called jasmine.clock().install() to create the clock.

And then we run the test code by calling tick to move to the time we want.

Then when the test is done, we call jasmine.clock().uninstall() to remove the mock clock.

Conclusion

We can test various kinds of async code with Jasmine.

Promises and callbacks are all supported.

And we can make timer code synchronous with a mock clock.