In this recipe, we will write a simple calculator widget. By testing, we will simulate end-user actions and assert the intended application behavior.
Before jumping on the test, we definitely need an application expecting user actions. Let it be a simple calculator that cannot do complex operations but will sum up two of the supplied numbers, as seen in the following screenshot:
To make it work, we subscribe a handler to the click
event on the Calculate button. The handler takes the values of the first and second fields, sums them up, and puts the result in the third field. The implementation of the code is as follows:
"strict mode"; this.utils = { /** * Fire a suplied event on a given element * @param {object} el instance of HTMLElement * @param {string} eventName */ trigger: function( el, eventName ) { var e = document.createEvent("Event"); e.initEvent( eventName, true, true ); el.dispatchEvent( e ); }, /** * Subscribe handler for event on a supplied element * @param {object} el instance of HTMLElement * @param {string} eventName * @param {function} handlerCb */ subscribe: function( el, eventName, handlerCb ) { el.addEventListener( eventName, function( e ) { handlerCb( e ); }, false ); } }; (function( global ){ var document = global.document, utils = global.utils; utils.subscribe( global, "load", function() { utils.subscribe( document.getElementById("calc"), "click", function( e ) { var num1 = document.getElementById("num1"), num2 = document.getElementById("num2"), sum = document.getElementById("sum"); e.preventDefault(); sum.value = parseInt(num1.value, 10) + parseInt(num2.value, 10); }); }); }( this ));
Let's save the source code into the calc.js
file and load it from the HTML layout:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Calc</title> <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet"> <style> .container { margin: 32px; } </style> </head> <body> <div class="container"> <form class="form-inline"> <input id="num1" type="text" class="input-small" placeholder="Summand"> <span>+</span> <input id="num2" type="text" class="input-small" placeholder="Summand"> <span>=</span> <input id="sum" type="text" class="input-small" placeholder="Sum"> <button id="calc" type="button" class="btn btn-primary">Calculate</button> <button type="reset" class="btn btn-danger">Reset</button> </form> </div> <script src="./js/calc.js"></script> </body> </html>
Now, we run this HTML in a browser and test it manually. With numbers typed in, the button clicked, and the result received, we are now ready to write an automated test for the actions we just performed.
Subscribe a handler to the DOM-ready event and get references on the involved elements. To mimic a user typing numbers and clicking the button, we need to refer to the inputs and the button. However, elements will be available only when the DOM tree is built. So, we obtain references on the elements in the handler, which is called on the DOM-ready event. For that, we use the helper introduced in the sample application as follows:
utils.subscribe( global, "load", function() { var calc = document.getElementById("calc"), num1 = document.getElementById("num1"), num2 = document.getElementById("num2"), sum = document.getElementById("sum"); });
Define a test scope and simulate the user behavior. Within the scope, we assign test values to inputs and trigger the click event. Since the application responds to the click event immediately, we can synchronously assert that the value in the result field equals the sum of numbers we supplied.
utils.subscribe( global, "load", function() { var calc = document.getElementById("calc"), num1 = document.getElementById("num1"), num2 = document.getElementById("num2"), sum = document.getElementById("sum"); QUnit.test( "Test that calc sums up typed in numbers by click", function( assert ){ num1.value = "5"; num2.value = "7"; utils.trigger( calc, "click" ); assert.strictEqual( sum.value, "12" ); }); });
Load the test runner in a browser and examine the results shown in the following screenshot: