Lesson 36. Testing your application

Continual maintenance of your application in production requires fixing bugs. Fixing bugs means writing new code. Writing new code has the unforgiving tendency to break existing functionality. In this lesson, you take some steps to prevent the breaking of working code by implementing tests on your Node.js application. Writing tests in Node.js is similar to testing in other platforms and languages. First, you learn how to write simple tests for a function in your application. Then you implement tests for the controller actions and models to cover the bulk of your application’s code. By the end of this lesson, you’ll have the fundamental skills you need to get started testing your Node.js application.

This lesson covers

  • Using core modules to write assertion tests
  • Writing a Node.js test with mocha and chai
  • Building and running tests for controller actions with chai-http
  • Implementing tests for your API
Consider this

Your recipe application is looking great in production, and you’ve gained development support from some local developers. Your application code is being worked on by multiple people, and the new developers don’t necessarily know how their implementation of new features will affect the features you’ve already built.

A new developer adds a new index action on the users controller. This new action doesn’t respond with all the user data you originally planned for, which affects your API and views. If you write tests for your index action specifying what data you expect it to return, new developers will have a point of reference regarding what functionality is allowed to change with their modifications.

36.1. Basic testing with core modules

In the tech industry, application testing is a standard practice. When you write some code with explicit functionality, you want to make sure that functionality doesn’t change unless it’s intended to change. To help ensure that your code isn’t accidentally affected by changes and new features that you implement (or that another developer implements), you can write tests. Tests contain three components:

  • Test data representing sample data that you’d expect to receive in your application
  • Expectations detailing what a function or series of operations should output, given your test data and application code
  • A testing framework to run your tests and determine whether your defined expectations were met

Before learning about some external tools that you can use to test your application, you can use a core module that comes with Node.js. The assert module offers some basic functions that you can use to confirm the equality of two values. You can think of these functions as being conditional statements wrapped in testing language.

You can use this module by navigating to a new project folder called simple_test and creating a new file called test.js with the code shown in listing 36.1. In this example, you require the assert module. Then you write an assertion test by using assert.equal to determine whether the first value, the result of a call to your custom add function, equals the second argument, 0. Last, you write the add function to take two values and return their sum. In this example, you expect the addition of 5 and 4 to equal 0. As you’d expect, this test should fail, and when it fails, the message in the final argument should appear in terminal.

Run this file to see the assertion error in terminal by entering node test within the simple_test project directory. That error should read AssertionError [ERR_ASSERTION]: 5 plus 4 should equal 9.

Listing 36.1. Simple assertion test in test.js
const assert = require("assert");                         1

assert.equal(add(5, 4), 0, "5 plus 4 should equal 9");    2

let add = (x, y) => {                                     3
  return x + y;
};

  • 1 Require the assert module.
  • 2 Write the assertion test.
  • 3 Implement the function specified in your test.

To correct this test, you need to change 0 to 9. You could also add another assertion test here to specify what your add function shouldn’t return. You could write assert.notEqual (add(5, 4), 0), for example. If this test ever fails, you’ll know that something is wrong with your add function that needs modification.

The assert module is a great way to start writing tests for Node.js. For your application, however, you’ll benefit from external packages that test more-complicated functionality. For more information about the assert module, visit https://nodejs.org/api/assert.html.

Test-driven development

Test-driven development (TDD) is an application development strategy in which tests specifying the expectations of your code are written first, followed by the feature implementation designed to pass your initial tests.

You want to make sure that your tests comprehensively cover your application’s functionality, which means writing tests that specify how your application should work when it’s provided valid and invalid data. Sometimes, when you write your tests after you’ve already implemented the application code, it’s easy to miss edge cases that aren’t accounted for in your test suite. For this reason, TDD can offer a more wholesome development experience.

TDD involves the following steps:

  1. Write your tests with sample data and expectations of the results, using that sample data through some method or function that you’ll build later.
  2. Run your tests. At this point, all your tests should fail.
  3. Implement code for your testing to behave according to the expectations you defined in your tests.
  4. Run your tests again. At this point, all your tests should pass.

If your tests don’t pass after you’ve written your application’s code, it could mean that your application code isn’t perfected yet.

If you were using TDD to implement a function called reverse that takes a string as a parameter and reverses it, for example, you might follow these steps:

  1. Write a test for the reverse function, using a test string, var s = "Hello", such that when you run reverse(s), you expect the result to be "olleH".
  2. Run the tests, and expect them to fail.
  3. Write the code to reverse strings.
  4. Rerun the tests until all of them pass.
Quick check 36.1

Q1:

What is an assertion test?

QC 36.1 answer

1:

An assertion test is code that you write to express your expectations of how some sample data might change, equal, or otherwise relate to another value. This test could be a comparison of two pieces of raw data or a comparison of data resulting from a function call or series of operations.

 

36.2. Testing with mocha and chai

To start testing your application, install the mocha and chai packages in your recipe-application terminal window by running the command npm i mocha -g and npm i chai -S. mocha is a testing framework. Much like Express.js, mocha offers a structure and methods that can be used in conjunction to test your application code. You install mocha globally because you need to use the mocha keyword in terminal, and you’ll likely test other projects. chai should be installed as a development dependency because you’ll be testing your code only locally; you don’t need this package to be installed in your production environment.

To use the mocha module, run mocha in your project’s directory in terminal. Running this command directs mocha to look for a test folder within your project folder. As with any framework, a conventional directory structure is used to keep your tests organized and separate from your other code files, so you need to create that test folder at the root of your application directory.

Note

Visit https://mochajs.org for more information about the mocha framework, from installation to use in terminal.

mocha helps you describe and run tests, but it doesn’t provide the tools you need to determine whether the outcomes of your code are what you expected. For that purpose, you need an assertion engine to run assertions, which describe how code should output a specified value.

chai is the assertion engine that you’ll use in this lesson. To use chai, require it in each test file you plan to run. Then, like the assert method from your core module, you can use expect, should, or assert as function verbs to check whether your code returns the intended results in your tests. For the following examples, use the expect function. chai also has descriptive functions to help you explain your tests before the assertions themselves. You’ll use the describe function to specify the module and function you’re testing.

Note

describe functions can be nested.

For the actual tests, use the it function to explain what you expect to happen in the test. Semantically, this function allows your test to read this way: In a specific module, for a specific function, your code (it) should behave in a certain way when it’s provided with some specific data. You take a closer look at this semantic structure in the next example.

The last steps in using these packages are creating the test file, requiring any custom modules with methods you want to test, and providing sample data within your tests. Write a simple test for your recipe application, using mocha and chai. Create a new file called usersControllerSpec.js in the test folder within your project’s directory. Per development convention, Spec is used in filenames to indicate a test suite.

Within this file, test the getUserParams function used in your user’s controller from the capstone exercise in lesson 25. For testing purposes, add the getUserParams function to usersController.js, as shown in listing 36.2.

Note

You can make use of this function in the create action by creating a new User instance with the following line: let newUser = new User(module.exports.getUserParams(req.body)). You can reference the getUserParamsthrough module.exports.

Unless you export this function, there’s no way for any other module to access the function.

Listing 36.2. Exporting the getUserParams function
getUserParams: (body) => {          1
  return {
    name: {
      first: body.first,
      last: body.last
    },
    email: body.email,
    password: body.password,
    zipCode: body.zipCode
  };
}

  • 1 Export getUserParams in usersController.js.

In usersControllerSpec.js, require chai along with usersController.js. The code for your test file resembles the code in listing 36.3. Because you use the expect assertion function, you can require it directly from the chai module; you won’t need chai for anything else. Then define your first describe block by stating the module you’re testing. The following describe block specifies the function you’re testing. Within that nested describe, you can run multiple tests that pertain to getUserParams. In this case, you’re testing whether getUserParams returns data that includes your name properties when provided a sample request body. The second test ensures that a blank request body results in an empty object. You use deep.include to compare the contents of one JavaScript object with another. For more information about chai assertion methods, visit http://chaijs.com/api/bdd/.

Listing 36.3. Exporting the getUserParams function in usersControllerSpec.js
const chai = require("chai"),
  { expect } = chai,                                             1
  usersController = require("../controllers/usersController");

describe("usersController", () => {                              2
  describe("getUserParams", () => {
    it("should convert request body to contain
 the name attributes of the user object", () => {             3
      var body = {
        first: "Jon",
        last: "Wexler",
        email: "[email protected]",
        password: 12345,
        zipCode: 10016
      };                                                         4
      expect(usersController.getUserParams(body))
        .to.deep.include({
          name: {
            first: "Jon",
            last: "Wexler"
          }
        });                                                      5
    });

    it("should return an empty object with empty request
 body input", () => {
      var emptyBody = {};
      expect(usersController.getUserParams(emptyBody))
        .to.deep.include({});
    });
  });
});

  • 1 Require the expect function.
  • 2 Define the focus of your test in a describe block.
  • 3 Detail your test expectations.
  • 4 Provide sample input data.
  • 5 Expect some object to be included in the results.

To run this test, enter the mocha command in your project’s terminal window. You should see an indication that both tests passed (figure 36.1). If you get an error or if a test fails, make sure that your modules are accessible from each other and that your code matches the code listings.

Figure 36.1. Displaying passing tests in terminal

Note

To exit your mocha test in terminal, press Ctrl-D.

In the next section, you implement a test that covers more than a single function.

Quick check 36.2

Q1:

What’s the difference between describe and it?

QC 36.2 answer

1:

describe wraps the tests that relate to a particular module or function, which makes it easier to categorize your test results as they appear in terminal. it blocks contain the actual assertion tests that you write.

 

36.3. Testing with a database and server

To test a web framework, you need more than some sample data and access to the modules you’re testing. Ideally, you want to re-create the environment in which your application normally runs, which means providing a functioning web server, database, and all the packages your application uses.

You aim to set up an environment in addition to your development environment. You can define a test environment through the process.env.NODE_ENV environment variable. At the top of any test file, add process.env.NODE_ENV = "test" to let Node.js know that you’re running your application in a testing environment. This distinction can help you differentiate between databases and server ports. If you’re running your application in the test environment, you can tell the application to use a recipe_test_db database and run on port 3001, for example. This way, you can test saving and retrieving data from a database without interfering with your development data or development server.

Now indicate to your application to use the recipe_test_db test database in the test environment and to otherwise default to the production and development databases, as shown in the next listing. In this example, you define a db variable earlier in the code and assign it to a local database. If the environmental variable, process.env.NODE_ENV, tells you that you’re in the test environment, the db variable points to your test database URL.

Listing 36.4. Separating environment databases in main.js
if (process.env.NODE_ENV === "test") "mongoose.
 connect(mongodb://localhost:27017/recipe_test_db", {               1
 useNewUrlParser: true});
else mongoose.connect(process.env.MONGODB_URI ||
 "mongodb://localhost:27017/recipe_db",{ useNewUrlParser: true });  2

  • 1 Assign to your test database while in the test environment.
  • 2 Default to the production and development databases.
Note

MongoDB creates this test database for you if it doesn’t exist.

You apply the same logic to your server port, as shown in the following listing. Here, you use port 3001 if you’re in the test environment. Otherwise, you use the normal ports that you’ve used so far.

Listing 36.5. Setting up a test server port in main.js
if (process.env.NODE_ENV === "test")
 app.set("port", 3001);                              1
else app.set("port", process.env.PORT || 3000);

  • 1 Assign the port to 3001 (test), default to port 3000 (production).

Last, you need to export your application contained in app by adding module.exports = app to the bottom of main.js. Exporting your application allows you to access it from the test files you write. Also, in your controller tests, you need the help of another package to make requests to your server. Install the chai-http package by running the npm i chai-http -S command to save this package as a development dependency.

With these changes in place, you’re ready to write a comprehensive test on your models and controllers. In the following examples, you test the user’s controller actions and User model. First, test the User model by creating a file called userSpec.js in your test folder with the code in listing 36.6.

In this file, you can create multiple tests on the User model. The first tests you write are to ensure that users can be created and saved to your database. You need to require the User module, mongoose, and chai. From chai, pull the expect function into its own constant so that your tests are more readable.

Next, implement the beforeEach function provided by mocha to remove any and all users from your test database before you run each test. This function ensures that the results of previous tests don’t affect other tests in this file. Your describe block indicates that you’re testing the save functionality on the User model. Your it block contains two expectations to determine whether you can successfully save a single user to the database. First, provide some sample data that your application might naturally receive as input data. Then set up two promises to save the user and find all users in the database. The inner nested promise is where you run your expectations.

Last, create two assertions where you expect the results of your promises to yield an array, where the second item contains all the users in your database. Because you created a single user, you expect the size of the array of users to be 1. Similarly, you expect the only user in that array to have an _id property, indicating that it has been saved to your MongoDB database. When your test is complete, call done to indicate that the tests are complete and promises are resolved.

Listing 36.6. Testing saving a Mongoose user in userSpec.js
process.env.NODE_ENV = "test";               1

const User = require("../models/user"),
  { expect } = require("chai");              2

require("../main");

beforeEach(done => {                         3
  User.remove({})
    .then(() => {
      done();
    });
});

describe("SAVE user", () => {                4
  it("it should save one user", (done) => {  5
    let testUser = new User({
      name: {
        first: "Jon",
        last: "Wexler"
      },
      email: "[email protected]",
      password: 12345,
      zipCode: 10016
    });                                      6
    testUser.save()
      .then(() => {
        User.find({})
          .then(result => {
            expect(result.length)
              .to.eq(1);                     7
            expect(result[0])
              .to.have.property("_id");
            done();                          8
          });
      });
  });
});

  • 1 Require necessary modules and set the environment as test.
  • 2 Assign a variable to the chai.expect function.
  • 3 Remove all users from the database before each test.
  • 4 Describe a series of tests for saving users.
  • 5 Define a test for saving a single user.
  • 6 Set up promises to save a user with sample data, and fetch all users from the database thereafter.
  • 7 Expect one user with an ID to exist in the database.
  • 8 Call done to complete the test with promises.

Run your tests by running the mocha command in your project’s terminal window. This command starts your MongoDB test database and saves a test user. If your test doesn’t pass, make sure that your modules are connected correctly and that users are saving in your application in the browser. It’s helpful to know that the user model works correctly, and you can add more tests to this file. You can use sample data that shouldn’t save or try saving two users with the same email address, for example. Your validations should prevent both users from saving.

Next, test a controller action. After all, the controller action connects your models and views, providing a lot more of the experience you’d like to preserve in your application. In the following example, you test the user index action, which fetches all the users in the database and sends those users to your view in the response body.

For this test file, you need to require chai-http by adding const chaiHTTP = require ("chai-http") and your main app module by adding const app = require("../main"). Then tell chai to use chaiHTTP by adding chai.use(chaiHTTP), and you’re ready to make server requests. In the following example, you use chai.request(app) to communicate with the server. To test the index action specifically, add the code in listing 36.7 to users-ControllerSpec.js in your test folder.

You can wrap your tests with a describe block indicating that the tests are for users-Controller. Another describe block specifies that the tests are for GET requests to /users.

Note

The first argument in describe is any string of your choice that explains what the tests are testing. You don’t need to follow the text shown in this example.

Your test to show all users in the database uses chai.request to communicate with your application, which in turn sets up a web server running at port 3001. Then you chain a get request with a chai helper method to reach the /users route. In your application, this should take you to the users index action in the users controller. You end your request with end and write your expectations on the response that’s returned from the server. You expect the response to have a status code of 200 and no errors.

Listing 36.7. Testing the users index action
describe("/users GET", () => {                          1
  it("it should GET all the users", (done) => {
    chai.request(app)                                   2
      .get("/users")
      .end((errors, res) => {                           3
        expect(res).to.have.status(200);                4
        expect(errors).to.be.equal(null);
        done();                                         5
      });
  });
});

  • 1 Describe your test block for the users index action.
  • 2 Make a GET request to your test server.
  • 3 End the request with a callback to run your expectations.
  • 4 Expect your application’s response status to be 200.
  • 5 Call done to complete the server interaction in your test.

Run this test by entering mocha in your project’s terminal window to see two tests pass. Your test suite contains all the tests contained in files in the test folder. If you want to test only usersControllerSpec, you can run mocha test/usersControllerSpec.

Quick check 36.3

Q1:

What does chai.request do?

QC 36.3 answer

1:

chai.request takes a Node.js web server and allows your test environment to make requests. These requests mimic the ones in your production application, allowing for a more integrated, comprehensive test of your code.

 

Summary

In this lesson, you learned about testing your Node.js application. You started with the assert core module and quickly jumped into testing your models and controllers with chai, mocha, and chai-http. With these tools and others, you’ll be able to re-create most of the actual experiences that users have with your application. If you can stay ahead by predicting user experiences and edge cases, and testing them before they go to production, you’ll face far fewer production crashes.

Try this

Writing a test suite isn’t a simple task, because you can write an endless number of tests. You want to make sure that you cover most scenarios in your application, using a variety of sample data.

Create a test module for each controller and model in your application. Then try to build describe blocks and tests for each action.

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

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