Unit Testing

FuncUnit

What is FuncUnit?

  • FuncUnit tests web applications with a simple jQuery-like syntax. Via integration with Selenium and PhantomJS, you can run the same tests automated.
  • FuncUnit is QUnit

FuncUnit is QUnit (+)

QUnit is jQuery's unit testing tool. Like JSUnit. Like JUnit.

 

FuncUnit uses QUnit for organizing tests and assertions. FuncUnit extends QUnit so that you can:

  • Open a web page
  • Query for elements
  • Simulate a user action
  • Wait for a condition to be true
  • Get information about your page and run assertions
  • Run tests in the browser
  • Integrate with browser automation and build tools

What makes up a test?

FuncUnit provides the basic structure needed to

write unit or functional tests.

Modules

Tests

Assertions

Tests

Tests are the individual building blocks of

your unit tests and are the "thing" you are testing.

test("No cookie, should land on login screen (ui only)", function() {
    expect(2);
    ok(S("input[name='user']"), "User field exists");
    ok(S("input[name='password']"), "Password field exists");
});

Modules

Modules are groups of tests with

setup and teardown methods that run for each test.

module("Contacts", {
  // runs before each test
  setup: function(){
    // setup code
  },
  // runs after each test
  teardown: function(){
    // cleanup code
  }
})

Assertions

Assertions are the essential element of any unit test.

 

The test author expresses the results expected (assertions) and FuncUnit compares the assertions to the actual values that an implementation produces.

test("counter", function() {
  ok(Conctacts.first().name, "there is a name property");
  equal(Contacts.counter(), 5, "there are 5 contacts");
});

Built-in Assertions

FuncUnit has several built-in assertions that you will use.

 

Here are a few of the more common ones you'll use but, there are others.

equal();
expect();
notEqual();
ok();
strictEqual()

Setting up your test

Sometimes you'll want to open a specific HTML page to test your code and other times you can use the default FuncUnit space to test your module. This is ESPECIALLY helpful when you need to test against a specific FIXTURE arrangement.

module("autosuggest",{
  setup: function() {
    S.open('autosuggest.html')
  }
});

"S" is a copy of the jQuery "$" method. It is used to find elements in the page you're testing. But, it works slightly differently than $.

The following uses S.open(URL) to open a file before every test.

The "S Method"

The S method accepts any valid jQuery selector string, just like $.

 

One difference from $ is that this query will happen in the context of the window of the page you're testing, not the FuncUnit page where it runs.

 

Depending on the context in which S is called, this selector may be used immediately and return a jQuery collection,

or

it may cache the selector, which will be used in the asynchronous queue later to find elements when previous queued methods have finished.

Why "S"?

S is a "copy" of $, created using jQuery.sub.

 

All the jQuery methods that FuncUnit doesn't

overload are callable on S collections.

 

The reason for this is to preserve jQuery in the test page, unmodified.

If you want to use jQuery, none of its

methods are modified.

 

jQuery can be used to do unit testing, or to directly access the test page and do custom things.

Keeping Tests Atomic

Split up your testes granular enough such that you don't affect subsequent tests with the results from previous tests.

 

Your tests should typically assume "start state" at the beginning of each test and set it up into the scenario necessary to isolate unit you're trying to test.

 

But, once you've split up all of your tests to keep them atomic and free of side effects, how do you keep them logically organized and be able to run a specific group of tests on their own?

Modules

Modules are groups of tests with setup and teardown methods that run for each test.

 

All tests that occur after a call to module("Module Name") will be grouped into that module. The test names will all be preceded by the module name in the test results. You can then use that module name to select tests to run.

 

For example...

steal(
   "funcunit",
   "wsawui/controllers/login/login.js",
function(S, Login) {
   module("Login", {
      setup: function() {
         $("#qunit-test-area").append("<div id='content'></div>");
      }
   });

   test("login view", function() {
      Login.index();
      ok(S("button#login"), "Login button exists");
      ok(S("button#resetPwd"), "Change Password button exists");
   });

   test("reset1 view", function() {
      Login.reset();
      ok(S("button#continue"), "Continue button exists");
   });

   test("reset2 view", function() {
      Login.reset2();
      ok(S("button#close"), "Close button exists");
   });

   test("password change view", function() {
      Login.changePwd();
      ok(S("button#changePwd"), "Change Password button exists");
   });
});
   module("Monitor", {
      setup: function() {
         $("#qunit-test-area").append("<div id='content'></div>");
      }
   });

   test("Monitor Dashboard Journey Map", function() {
      expect(3);
      monitor.dashboard({
         subaction: "journeyMap"
      });
      ok(S("#monitor-journey-view").visible, "Journey View exists");
      ok(S("#monitor-map-view").visible, "Map mode visible");
      ok(S(".module-actions #monitor-mode .list-icon").visible, "List icon visible");
   });

   test("Monitor Dashboard Journey List", function() {
      expect(4);
      monitor.dashboard({
         subaction: "journeyList"
      });
      ok(S("#monitor-journey-view").visible, "Journey View exists");
      ok(S("#monitor-list-view").visible, "List mode exists");
      ok(S(".module-actions #monitor-mode").visible, "Map icon visible");
      ok(S(".module-actions #monitor-mode .list-icon").missing, "List icon not visible");
   });

But what about Fixtures?

Fixtures are Freeing

Fixtures give you the ability to not depend upon REAL data in REAL test levels with REAL backend to complete your UI work.

Fixtures but you in Control

The fact is...
creating stand-alone automated unit tests is time-consuming & seemingly impossible.

Fixtures for Manual UT

Being able to get the results you need in some obscure data scenario when the data doesn't really exist is key.

FixtureManager

"testData" vs "realData"

 

What's the difference?

FixtureManager: testData

testData is a URL parameter. It is an array of fixture actions separated by commas.

testData=hasRouteGeofences,routeTriggerRouteAtProcess,success1

Creating Fixtures

  • Every REST endpoint MUST have a json Fixture response file in models/fixtures/responses

  • The name of the files must be one of the following:

    • The endpoint name (e.g., session.json)

    • The action name (as defined in CXSsettings.js)
      (e.g., member.orgList.json

 

NOTE: The application will bomb if the Fixture response file is NOT in place & the interface is defined in CXSsettings.js (& have fixtures turned on).

FixtureManager: testData

fixture "actions" are either:

1. The REST endpoint name (e.g., session)

2. Whatever name you gave it for clarity or necessity in the entry for the endpoint in CXSsettings.js (e.g., member.orgList)

These "actions" must have a corresponding response file by the action name + extension json in the modules/fixtures/responses directory.

Unit Testing

By stephanieshusband

Unit Testing

  • 466