Node REST API Testing with Jasmine

In last article REST using Node/Express & Mongoose , I talk about setting REST APIs in NodeJS, Express & Mongoose. Today I will discuss unit testing for REST APIs using Jasmine.

Jasmine is behavioral testing framework for javascript applications. It provide easy to write & understand testing rules. While it can be downloaded from https://github.com/jasmine/jasmine. In node js we would use npm package named jasmine-node.

$ npm install --save jasmine-node

We also install request package for easy http calls.

$ npm install --save request

Now create a test folder let we name it spec. We can define our test cases in this folder. Let create a file names hello.spec.js and Put following code into it

describe("Hello World Suite", function() {
    it("check if text is 'Hello World'", function(done) {
        let text = "Hello World";
        expect(text).toBe("Hello World");
        done();
    });
   it("check if another text is 'Hello World'", function(done) {
        let text = "Not Hello World";
        expect(text).toBe("Hello World");
        done();
    });
});

Now we run command

$ node_modules/.bin/jasmine-node spec

This command generate assert failures for second test like below

Failures:

1) Hello World Suite check if another text is 'Hello World'
 Message:
 Expected 'Not Hello World' to be 'Hello World'.

2 tests, 2 assertions, 1 failure, 0 skipped

If we change text in second test from “Not Hello World” to “Hello World”  and re run command. We will get assert success like below .

2 tests, 2 assertions, 0 failures, 0 skipped

In above test “describe” work as Test Suite, So it can have many related tests, Let example for particular form field data validations, So all validation like not-empty, data type, duplicate can be part of single suite. describe have definition which show use of suite

individual tests inside described via “it”, which have suite description as first argument and callback as second.

We need to statement for compile a test. First a expect statement which is test assertion and other done function which notice the framework to completion of test. We can do many types of assertions

assert().toBe()
assert().toBeDefined() AND assert().toBeUndefined()
assert().toBeFalsy() AND assert().toBeTruthy()
assert().toBeNull()
assert().toEqual()
assert().toMatch()
assert().toThrow()

AND More....

Let we write test for previously REST APIs. Create a file called users.spec.js in spec folder.

var request = require("request");

var base_url = "http://localhost:3000/users"

let User = {
   _id:1,
   name:'',
   email:'',
   password:''
};

describe("Users List API Exists", function() {
     describe("GET /users", function() {
         it("returns status code 200", function(done) {
             request.get(base_url, function(error, response, body) {
             expect(response.statusCode).toBe(200);
             done();
         });
    });
 });

Above test check whether List API is available, It verify this by checking response status code is equal to 200 or not. Add another test to inner describe

it("API Response should be valid json", function(done) {
    request.get(base_url, function(error, response, body) {
        expect(() => {
            JSON.parse(body);
        }).not.toThrow();
        done();
    });
 });

Above test check if response is valid json or not. When invalid json as response return the native JSON parse function throw error which handled by expect ” not -> toThrow”. So assertion failed while in case of valid json assertion passed.

it("API Response should be valid array of user objects", function(done) {
     request.get(base_url, function(error, response, body) {
        let users = JSON.parse(body);
        const userRows = users.map((userRow) => {
            expect(JSON.stringify(Object.keys(User).sort()) ===     JSON.stringify(Object.keys(userRow).sort())).toBeTruthy();
        });
        done();
    });
 });

Above test goes further more and check return fields is valid. This test can be ignored in case of no restriction for fields returned in response.

I will discuss more on jasmine on API testing on further articles.

All code is hosted on Github

REST using Node/Express & Mongoose

NodeJS is fast growing language in today environment. For web their are several frameworks but Express web framework  is most used framework for web development in node js.

So how to make a sample Rest API in express / Node JS ?

First setup express using npm. So we use following commands to get npm init & require packages install.

# add package json
$ npm init

$ npm install --save express
$ npm install --save mongoose body-parser

Above commands install express framework in node modules. Then it install http request parser package plus ODM package for mongo db called mongoose. Now it is time to do some coding. First create main application file app.js & put following code.

var express = require('express');
var app = express();
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test');


// var UserController = require('./controllers/UserController');
// app.use('/users', UserController);

var port = process.env.PORT || 3000;
var server = app.listen(port, function() {
      console.log('Express server listening on port ' + port);
});

module.exports = app;

Update package.json to set tell about main script

"scripts": {
       "start": "node app.js"
 }

You can run this code by

$ npm start

Which start the server at 3000 port. You can check http://localhost:3000 which show “Cannot GET /” because we did not setup router. So we add a User Module. for this we need to add controller & model. So create two directories controllers & models for better code maintaining.  Add Following mongoose based code in models/User.js file.

var mongoose = require('mongoose');
var UserSchema = new mongoose.Schema({
    name: String,
    email: String,
    password: String
});
mongoose.model('User', UserSchema);
module.exports = mongoose.model('User');

Now Setup controller file controllers/UserController.js which also work as router for User APIs.

var express = require('express');
var router = express.Router();
var bodyParser = require('body-parser');
router.use(bodyParser.urlencoded({ extended: true }));

var User = require('../models/User');

router.post('/', function (req, res) {

    User.create({
        name : req.body.name,
        email : req.body.email,
        password : req.body.password
    },
    function (err, user) {
        if (err) return res.status(500).send("There was a problem adding the information to the database.");
        res.status(200).send(user);
    });

});

// RETURNS ALL THE USERS IN THE DATABASE
router.get('/', function (req, res) {

    User.find({}, function (err, users) {
        if (err) return res.status(500).send("There was a problem finding the users.");
        res.status(200).send(users);
     });

});

// RETURNS USER DETAILS IN THE DATABASE
router.get('/:id', function (req, res) {

    User.findOne({"_id":req.params.id}, function (err, users) {
        if (err) return res.status(500).send("There was a problem finding the users.");
        res.status(200).send(users);
    });

});

// UPDATE USER DETAILS IN THE DATABASE
router.put('/:id', function (req, res) {

    User.update({"_id":req.params.id}, {
        name : req.body.name,
        email : req.body.email,
        password : req.body.password
    }, function (err, users) {
        if (err) return res.status(500).send("There was a problem finding the users.");
        res.status(200).send(users);
    });

});

// DELETE USER FROM THE DATABASE
router.delete('/:id', function (req, res) {
    User.remove({"_id":req.params.id}, function (err, users) {
        if (err) return res.status(500).send("There was a problem finding the users.");
        res.status(200).send(users);
     });

});

module.exports = router;

Now we have out two user APIs ready for insert a single record ( POST ) & get all records ( GET ). But still we need to register router with app, So uncomment lines in app.js

var UserController = require('./controllers/UserController');
app.use('/users', UserController);

Now we have http://localhost:3000/users working as POST & GET method for our APIs. We can test it by invoking respective methods via CURL CLI.

$ curl http://localhost:3000/users
$ curl -X POST -d 'email=kuldeep@gmail.com&name=Kuldeep&password=kamboj' http://localhost:3000/users

$ curl http://localhost:3000/users/5a47d942cb4f00c95de8f511

$ curl -X PUT -d 'email=kuldeepk@gmail.com&name=KuldeepK&password=kamboj' http://localhost:3000/users/5a47d942cb4f00c95de8f511

$ curl -X DELETE  http://localhost:3000/users/5a47d942cb4f00c95de8f511