Sencha Ext JS 7.7 is Here – Discover What’s New and Exciting – LEARN MORE

Sencha Test Examples – Part 1

May 5, 2016 111 Views
Show

Introduction

Sencha Test enables developers to write and execute tests on their machines and then easily automate their tests in a build automation system. The best way to learn how to write unit tests is to see examples. In this article, we’ll walk through the new Unit Test example in the SenchaTestDemo repository and see how easy it is to get started.

Unit Tests

Whether you use a Test Driven Development (TDD) or Behavioral Driven Development (BDD) development process, or you’re just trying to ensure your code works, unit testing is a best practice for all developers. Unit testing takes the smallest pieces of a system (units) and tests them in isolation to ensure they are working correctly. Unit testing is an easy way to greatly improve application quality by catching many bugs early.

To illustrate how that can be accomplished in real applications, we’ll consider one of the smallest pieces of an application: a data model. For simplicity, we’ll use the model for the records in the TODO List of the Admin Dashboard example.

TODO List of the Admin Dashboard example

The important code in Admin.model.Todo is the following set method:

    set: function (name, value) {
        var data = name;

        if (typeof name === 'string') {
            if (name !== 'done') {
                data = [name, value];
            } else {
                data = [{
                    completedDate: value ? new Date() : null
                }];
                data[0].done = value;
            }
        } else {
            if (data.done !== undefined) {
                data = [Ext.apply({
                    completedDate: data.done ? new Date() : null
                }, data)];
            } else {
                data = [name];
            }
        }

        return this.callParent(data);
    }

The above set method enforces a basic business rule:

   Todo’s that are done have their completeDate field set while those that aren’t done do not.

This approach ensures that whichever user interface edits the done field of a record, the completeDate will also be updated correctly. How can we know that this method is doing its job? Write some unit tests and exercise it!

Examining the code, there are four dimensions to consider: how the method is called, which field(s) are being set, the value being applied, and the initial state of the record.

Different Call Forms

The set method can be called in two different ways: passing name and value as separate arguments, or passing an object of name/value pairs as a single argument.

Affected Fields

The set method should only respond to changes in the done field. Changes to other fields should not affect the completedDate field. Tests that modify the done field and verify the effect on completedDate are called positive tests, while tests that modify other fields and verify that there is no change to completedDate are called negative tests. It’s very easy to forget to add negative tests. It helps to remember, however, that it is equally important to know a class does not do things it should not do (negative) as it is to know that it does what it should (positive).

New Field Value

Setting the done field to true should put the current date in the completedData field, while setting done to false should set completedDate to null.

Initial Field Value

Changing the initial value of done should result in both done and completedDate being marked as dirty or modified.

Strategy

There are many ways to combine these dimensions and test each combination, but there is often one way that leads to a simpler test suite. In this case, the simplest expression is to first break up the tests along the initial record value dimension.

   describe('Todo Tests', function() {
      describe('with a not completed todo item', function() {
           beforeEach(function () {
               this.startTime = new Date().getTime();
               // Create a not completed item
               this.todo = new Admin.model.Todo({
                   id: 99,
                   task: 'Do this now',
                   done: false
               });
           });

In Jasmine the beforeEach function accepts a function that should be called before each test in the suite. In the above code, the Jasmine-provided context object (“this” pointer) is used to hold a todo record instance that has done set to false. Because this is part of the first suite of two top-level suites, this record state is the base for the first half of the test combinations.

For the second dimension, we use the form of the set method call. The other dimensions are the tests themselves.

    describe('set fields by name', function() {
        it('should not set done or completedDate when changing another field', ...
        it('should set completedDate when done is set', ...
        it('should unset completedDate when done is unset', ...
    });
    describe('set fields by object', function() {
        it('should not set done or completedDate when changing another field', ...
        it('should complete the todo', ...
        it('should unset completedDate when done is unset', ...
    });

A very similar, but not identical, suite of tests is repeated for the second value of the primary dimension:

    describe('with a completed todo item', function() {
        beforeEach( function() {
            this.startTime = new Date().getTime();
            this.todo = new Admin.model.Todo({
                id: 99,
                task: 'Do this now',
                done: true,
                completedDate: new Date()
            });
        });

To see the full test case, check out the example on GitHub.

Summary

This test suite covers all possible forms of initial state and transition possibilities as well as negative cases that should have no side effects. In total, the set method is approximately 20 lines of code. Because of its highly branching nature, however, it took almost 250 lines of test code to fully cover all of its dark corners. Even though these 250 lines were mostly very simple expectation statements, the ratio of test code to application code will often tend to grow in this direction.

In other words, writing unit tests is an investment, but an investment that pays dividends. Here are some guidelines to consider that should help maximize those returns while minimizing the investment.

  • Look at the tricky bits. Simple methods are often verifiable by inspection.
  • Look at the important bits. Not all areas of the code are equally important to the overall function of the application. Invest in tests for code that you cannot afford to see go wrong.
  • Look at the used bits. Spending time testing code areas that are rarely used does not provide much return on your investment. But even if something is used rarely, it may still be important.
  • Keep tests simple. When debugging a test failure you don’t want to struggle with the complexity of the tests. Even worse, you don’t want bugs in the tests themselves.
  • Test positive and negative conditions. Be sure the code does not do things it should not be doing.
  • Focus on inputs and outcomes, not internal details. Tests should survive a rewrite of the internals so long as the inputs, public methods, and outputs are the same.
  • Be sure to clean up any resources that won’t simply be garbage collected (such as elements added to the DOM). Use afterEach or afterAll Jasmine APIs to ensure proper clean-up happens even when exceptions are thrown.
  • Conclusion

    I hope this example of unit tests has shown the usefulness and the relative ease of creating them, and you’re inspired to write your own tests. Using Sencha Test, you can run your unit tests locally in Sencha Studio or via the command line stc utility. Sencha Test makes it easy to take your tests from development into your continuous integration (CI) / build system and put a safety net under your development process. In part 2 of this series, I’ll show you some examples of how tests can be written and executed locally on the your machine and then easily replicated in a continuous integration (CI) environment.

coming soon

Something Awesome Is

COMING SOON!