mercredi 27 mai 2015

CasperJS UI testing best practices

CasperJS is an open source UI testing tool you can plug over PhantomJS, the famous webkit headless browser. From javascript code, you can simulate in a test all possible user interactions with your web interface. For example you can click on a link, fill and submit a form, and check your DOM elements.

Just to be clear : as you interact directly with the browser, you can use CasperJS whatever is your server side technology.

I started to use this tool approximatively two years ago and wanted to share some best practices I use in my UI tests.

#1 : structure your tests with Page Object Pattern


This tip is definitively the most important. UI tests are essential but have a main drawback : when your DOM changes, your tests are broken.

The concept of the Page Object Pattern is to have two kinds of objects in your tests : the Page objects and the scenarios :

  • Each page object represents a real page of your web application and is an abstraction of the page DOM
  • A scenario uses several page objects to describe the user navigation and has no idea on how are structured the web pages

Applying this pattern, you will centralize in a single place the CSS or XPATH selectors of a page. If you change the page DOM, you won't need to modify several tests but only the corresponding Page object.

I already covered this topic in another thread where you will find a complete example of the Page Object Pattern with CasperJS.

#2 : use Resurrectio recorder


Resurrectio is a useful Chrome plugin that records your actions in order to generate a CasperJS script : just activate it, do your actions on your web interface, then generate the script and your test is ready, or almost...

The plugin is not perfect and as for every recorder, you will need to refactor the code (especially with Page Object Pattern). Plus, some events are forgotten by the recorder and you will need to fix some CSS selectors. BUT it is still very useful and I use it as soon as I want to create a new test on new web pages.

#3 : empower your screenshots


With CasperJS, you can do at every moment in your test execution a screenshot of your web page. For example :

casper.then(function() {
    this.capture('beforeFillingTheConnectionForm.png');
});

You can still give a comprehensive name to your screenshot file like in my example but when you will debug a failing test, you will quickly need some additional information. The best you can do here is to create an utility method where you can customized all screenshot filenames. From that you will do very powerful screenshots :

exports.capture = function(screenshotName) {
   casper.then(function() {
    var testFile = this.test.currentSuite.file;
    if (testFile.indexOf('/') != -1) {
     testFile = testFile.substring(testFile.lastIndexOf('/') + 1);
    }
    testFile = testFile.replace('.js', '');

    var testName = this.test.currentSuite.name;

    var screenshotFile = 'screenshots/' + testFile + '/' + testName 
               + '/' + screenshotName + '.png';
    this.capture(screenshotFile);
   });
};

With this utility method, your screenshots are now saved in a folder screenshots/<testFilename>/<testName>. For example "screenshots/connectionScenario/Should connect then disconnect/".

As far as I know, this.test.currentSuite object is not documented and I cannot assure you that CasperJS contributors will keep this variable in the future CasperJS versions.

#4 : manage several environments


You surely need to execute your tests on several environments : local, tests, preproduction and so one. To manage that, you can create a config file per environment to store the environments urls or for example the user credentials. Then you set a variable telling which environment you want to use directly in CasperJS command line.

For example, you can have your local configuration in the file "config/env/local.js" :

{
  url: 'http://localhost:8080',
  login: 'dev',
  password: 'dev'
}

Your preproduction configuration in the file "config/env/preproduction.js" :

{
  url: 'http://preproduction',
  login: 'admin',
  password: 'admin'
}

Then in a file "config/config.js", you can load the good environment file according to the command line arguments :

var environment = casper.cli.get('env');

if (!environment) {
 console.log('Usage: "casperjs test --env= "');
 casper.exit(1);
}

console.log('Loading the ' + environment + ' config.');

var environmentConfig = require('./env/' + environment + '.js');
module.exports = environmentConfig;


#5 : use your favorite building tool


On a Java based web, project I started to execute CasperJS directly from Maven thanks to this thread. It is interesting because Maven directly handles PhantomJS and CasperJS installation. Quite appreciable for your development workstations and for your favorite continuous delivery tool.

I haven't tried yet Grunt or Gulp implementations but you will easily find that on Google ;-)

Conclusion


I hope you enjoyed these tips. Don't hesitate to give me your opinion on this thread or to share your own best practices with me.

Happy UI testing !



samedi 2 août 2014

Develop and test a Node.js HTTP client

Call an external HTTP service is a very common task when we develop a Node.js application. In production, an external service can be instable and it is essential that this instability does not broke your application. Whatever the project or the external service, cases to take into account are always the same.

From a simple use case, we will see in this article what are the different steps to follow in order to create a robust HTTP client in Node.js.

Our example

Our goal is to create a method countEmployees returning the result of the following HTTP request :

GET http://localhost:3000/employees/count
{"count":1200}

Step 1 : nominal case


In this step, we will write a first implementation of our client and test the case where the external service responds correctly.

To begin, using mocha, let's create a test which verifies that the value returned by the client is directly the value returned by the external service.

describe('employees count service', function() {
  it('should return 1200', function() {
 
    client.countEmployees().then(function(count) {
      count.should.equal(1200);
    }).done();
  });
});

Then, write a first implementation of the countEmployees method :

var Q = require('Q'),
  request = require('request'),
  config = require('./config');

exports.countEmployees = function() {
  var deferred = Q.defer();

  var options = {
    url: config.employeeCountUrl,
    json: true
  };

  request(options, function (error, response, body) {
    var employeesCount = body.count;
    deferred.resolve(employeesCount);
  });

  return deferred.promise;
};

We use in this implementation two modules : request for the HTTP requests and Q for the promises.

Here our test do a real HTTP call to the external service. To make the test pass, the HTTP service must be available and always return the same value. So it becomes essential to simulate this service. To do that, we will use nock module which allows to mock HTTP access in Node.js.

Starting with nock is easy thanks to its recorder. Firstly let's add the following instruction in our test :

beforeEach(function() {
  nock.recorder.rec();
});

During the test execution, we discover these lines in the console :

<<<<<<-- cut here -->>>>>>
nock('http://localhost:3000')
  .get('/employees/count')
  .reply(200, {"count":1200}, { 'x-powered-by': 'Express', 'content-type': 'application/json; 
charset=utf-8', 'content-length': '14', etag: 'W/"e-2043423703"', date: 'Wed, 09 Jul 2014 20:59:20 GMT',
connection: 'keep-alive' });
<<<<<<-- cut here -->>>>>>

You just have to copy past these lines in your test to simulate the execution of the request http://localhost:3000/employees/count. Don't hesitate to clean what you don't need, like for example the response header.

Another useful nock instruction is this one : nock.disableNetConnect() which allows to forbid all HTTP access. Our test becomes :

describe('employees count service', function() {
  beforeEach(function() {
    nock.disableNetConnect();
  });
 
  it('should return employees count', function() {
    nock('http://localhost:3000')
      .get('/employees/count')
      .reply(200, {count:1986});
 
    client.countEmployees().then(function(count) {
      count.should.equal(1986);
    }).done();
  });
});

And now, when the client executes the request, it is not the service which answers but nock. Moreover, we have the guarantee that we don't do extra HTTP requests. Now, shutdown the HTTP service and relaunch the test : it passes !

Our first goal is reached : we have a first implementation of our client and a test covering the nominal case.

Step 2 : service in error


Using nock, it is now easy to test the error cases. In this second step, we want to handle the case where the service returns an HTTP error. Let's add a new test and simulate the case where the service returns an error 500.

it('should return an error if http service is in error', function(done) {
  nock('http://localhost:3000')
    .get('/employees/count')
    .reply(500, {});
 
  client.countEmployees().then(function(count) {
    done(new Error('method should return an error'));
  }).catch(function() {
    done();
  });
});

The test doesn't pass. Indeed, the client doesn't return an error but a null result. Don't panic, we can easily handle this case and return an error if the http status code is not in the 200 range :

if (response.statusCode >= 300) {
  deferred.reject(new Error('Service has an invalid status code : ' + response.statusCode));
}

Step 2 goal is reached : our client can now handle HTTP errors.

Step 3 : service returns unexpected data


In this third step, we want to check that the client works correctly if the service returns unexpected data. In a new test, we will simulate that the service returns no count field but another field. With nock again, it is easy :

whenEmployeesCountIsCalled().reply(200, {nb:1986});

Please note that we factorized the nock call with the following method :

var whenEmployeesCountIsCalled = function() {
  return nock('http://localhost:3000')
    .get('/employees/count');
};

The test fails. To fix it, you can test that the expected field is in the HTTP request :

if (!employeesCount) {
  deferred.reject(new Error('Service did not return employees count'));
}

Step 3 goal is reached : we know how to handle unexpected data.

Step 4 : service is slow


In this step, we want to test the case where the external service is too slow. We can also do that with nock :

whenEmployeesCountIsCalled()
  .delayConnection(1500)
  .reply(200, {count:1986});

DelayConnection instruction allows to delay the HTTP answer of 1500 ms. Here we want that our service interrupts the connection after, for example 500 ms. With request module, you can configure a timeout :

var options = {
  url: config.employeeCountUrl,
  json: true,
  timeout: 500
};

Then you have to test if the error object is defined :

if (error) {
  deferred.reject(error);
  return;
}

The test passes. Step 4  is finished : our client is protected from slow access.

Step 5 : service is unavailable


In this last step, we want to check our client behavior with an unavailable service. Current version of nock cannot help to test this case. However we just need to do a real HTTP access on an unexisting host :

it('should return an error if service is unavailable', function(done) {
  client = rewire('../client/employees.client');
  client.__set__('config', {
    configEmployeeCountUrl: 'http://doesnotexist.localhost:3000/employees/count'
  });
 
  client.countEmployees().then(function() {
    done(new Error('method should return an error'));
  }).catch(function() { 
    done();
  });
});

Here we use rewire module to override the config object and give an unexisting url. The test passes. Indeed the fix from step 4 allows also to handle this error. Our goal is reached, we finalized the implementation of our HTTP client !

Conclusion 

Unavailable, slow, broken... production hazards are large and it is important that your application stays stable in these cases. Thanks to nock, we can easily reproduced these errors and build a robust code.

We covered only a few features of nock. To go further, don't hesitate to read the full documentation here : https://github.com/pgte/nock.

To finish, you will find the complete code on the following github respository : https://github.com/jsebfranck/node-http-example.






Translated from a Xebia article I wrote (in french)