The mabl blog: Testing in DevOps

Using Mocks for Testing in Your CI/CD Pipeline

Written by Bob Reselman (Guest Author) | Nov 15, 2017 3:45:00 AM

The Problems of Test Driven Development

A common problem that developers committed to Test Driven Development (TDD) have is testing code that has dependencies which are under development or unavailable. This is where Mocks come in. For example, take a look at Figure 1 below. The Customer object on the left side of the illustration depends upon functions found in the User object on the right.

Figure 1: Well designed software encapsulates code according to areas of concerns

There is a problem. Work is at a standstill. The developers of the User object cannot complete their work because they are waiting for the Data Team to finish working on the table and schema designs in the database. Thus, the Customer developers are waiting on the User developers and the User developer are waiting on the Data Team. Clearly the situation is unacceptable. But, what’s to be done? The answer is to use mocks.

A mock is a temporary object or service that emulates behaviors that are expected to be delivered later on.

Using a mock object allows the developer to perform testing on his or her work, while other development is underway. In the case illustrated in Figure 1, previously, the developer can test the function, Customer.update(user_data), despite the fact that an external dependency(s) might not be available. The developer simply mocks the dependency, in the case, User. Listing 1 below shows a developer defined mock, MockUser. As the name implies, MockUser mocks the expected behavior in User.

module.exports = new MockUser;

 

function MockUser(){

  this.validate = function validate(user_data){

      return user_data ? "OK" : "ERROR";

  };

 this.get = function get(user_data){

      if( user_data){

          user_data.id = 1;

          user_data.isMock = true;

          return user_data;

      }

      return "ERROR";

  };

  this.create = function create(user_data){

      user_data.id = 1;

      return user_data;

  };

  this.update = function update(user_data){

      return user_data ? "OK" : "ERROR";

  };

};

Listing 1: A simple developer defined mock object in Node.JS

Listing 2 shows a way to implement mocking into an object that has external dependencies. In this case we’ll use a MockUser directly within the Customer object. The Node.JS code in Listing 2 checks to see if an environment DEV_ENV exists and have been set to the value, test. If so, the mock is used.

'use strict';

 

const mockUser = require('../mocks/mockUser');

const user = require('./user');

module.exports.update = function update(user_data){

  let u = user;

  //check the development environment to see if we're in test

  if(process.env.DEV_ENV && process.env.DEV_ENV === 'test'){

      //if we are, use the mock

      u = mockUser;

  }

  let status = 'UNKNOWN'

  if(u.validate(user_data) === 'ERROR') return 'ERROR';

  // Check user exists

  const model = u.get(user_data);

  // Model create/update code

  if (model.id) {

      status = u.update(user_data);

  } else {

      status = u.create(user_data);

  }

  return status

}

Listing 2: Using a mock in Customer.js

Once the mock is in place in Customer.update(), the Customer code can now be tested as shown in Listing 3:

'use strict';

 

const chai = require('chai');

chai.should();

const assert = require('chai').assert;

const expect = require('chai').expect;

const describe = require('mocha').describe;

const it = require('mocha').it;

const customer = require('../models/customer');

describe('Basic Tests: ', () => {

  it('Can update customer', done => {

      const data = {};

      data.id = 1;

      data.firstName = 'Bob';

      data.lastName = 'Reselman';

      data.dob = new Date('7/29/1906');

      assert.equal(customer.update(data), 'OK', `Could not get status for ${data}`);

      done();

  });

  it('Cannot update customer', done => {

      assert.equal(customer.update(), 'ERROR', `Could not get status when passing null data to the function`);

      done();

  });

});

Listing 3: Mocking behavior in Customer allows the object to be tested

Mocking has been an important part of software testing for a long time. Design patterns for using mocks are well known. In fact, a whole industry has evolved around developing and using mocks. Most developers committed to unit testing use mocking frameworks since mocking can get quite complex very quickly. There is little sense in reinventing the wheel when a tried and true mock framework exists. Using a mock framework saves time and encourages developers to follow best practices. At mabl, we use mocking services for two different parts of our application. Mockito serves as the mocking tool we use when unit testing different parts of our application connecting to back end data components. Mockito is designed for Java applications and is a popular tool among Java developers. In addition, given our front end uses Javascript with Node and a React framework, we use Sinon for mocking. This link provides a comprehensive list of mocking frameworks available.  

Get the Code

You can find the source code that demonstrates how to make a developer defined mock on GitHub, here.

 

Mocking Services

In addition to using mocks to work with source code, you can also use mocks to emulate API behavior. For example, should your client side code have a dependency that makes GET requests to an API endpoint that is under development, you can use mock technology to emulate the request/response behavior of the API endpoint. For example, should you be developing an API using Java, you can use WireMock to mock your API. For .NET you can use mock4net and for Python there is, mock-server. The PHP community offers http-mock.

One of the better techniques for implementing mock services is to ensure that your development methodology supports the Specification First approach to API design. Using Specification First means that you design your API using one of the common API specifications, RAML, OpenAPI or API Blueprint. Then once the design is created using a specification, you can use community tools to automatically create simple mocks servers against that specification. You can use a tool such as SwaggerHub to auto-generate a mock web server using a specification written OpenAPI. SwaggerHub allows you to generate the server in a variety of languages. go-raml-mocker allows you create mock web servers against a specification written in RAML. For those shops creating specification under API Blueprint, there is api-blueprint-mockserver.

Putting It All Together

Mocking is a powerful technique that is used by many development shops dedicated to comprehensive testing in general, and Test Driven Development (TDD) in particular. Using mocks speed up development time while not sacrificing code quality. It takes a bit of time and attention to get comfortable using mocks. But, once the concept is understood and mocking techniques are mastered, developers, test engineers and testers will experience significant benefits. Mocking provides the power and flexibility to rapidly implement effective testing that is well suited for the Continuous Integration/Continuous Delivery pipeline. Adding mocks to your testing toolbox will improve both the value of the test you write and the quality of the code you make.