Getting your Hooks into Cordova

*
*
* Who Am I?
* State of the Room?
* CF API
* Ways to test your API?
* Overview of Testing Tools
* Using Testing in your Workflow
* Installing Jasmine
* Installing Testbox
* Live Demo
*
* Gavin Pickin – developing Web Apps since late 90s
* New Addition to Ortus Solutions
* ContentBox Evangelist
* What else do you need to know?
* Blog - http://www.gpickin.com
* Twitter – http://twitter.com/gpickin
* Github - https://github.com/gpickin
* Lets get on with the show.
*
* A few questions for you guys
* If you have arms, use them.
*
*Most CF Apps are moving towards
providing an API for multiple consumers
*CF has many REST API Solutions and
even more with CF 12 coming soon
*Built in CF
*Built in Railo/Lucee
*Coldbox API
*Taffy
*
*Click around in the browser yourself
*Setup Selenium / Web Driver to click
around for you
*Structured Programmatic Tests
*
*Black/White Box
*Unit Testing
*Integration Testing
*Functional Tests
*System Tests
*End to End Tests
*Sanity Testing
*Regression Test
*Acceptance Tests
*Load Testing
*Stress Test
*Performance Tests
*Usability Tests
*+ More
*
*
*Integration Tests several of the
pieces together
*Most of the types of tests are
variations of an Integration Test
*Can include mocks but can full end
to end tests including DB / APIs
*
“unit testing is a software verification
and validation method in which a
programmer tests if individual units of
source code are fit for use. A unit is the
smallest testable part of an application”
- wikipedia
*
*Can improve code quality -> quick error
discovery
*Code confidence via immediate
verification
*Can expose high coupling
*Will encourage refactoring to produce >
testable code
*Remember: Testing is all about behavior
and expectations
*
*TDD = Test Driven Development
*Write Tests
*Run them and they Fail
*Write Functions to Fulfill the Tests
*Tests should pass
*Refactor in confidence
*Test focus on Functionality
*
*BDD = Behavior Driven Development
Actually similar to TDD except:
*Focuses on Behavior and Specifications
*Specs (tests) are fluent and readable
*Readability makes them great for all levels of
testing in the organization
*Hard to find TDD examples in JS that are not
using BDD describe and it blocks
*
Test( ‘Email address must not be
blank’, function(){
notEqual(email, “”, "failed");
});
*
Describe( ‘Email Address’,
function(){
It(‘should not be blank’, function(){
expect(email).not.toBe(“”);
});
});
*
expect(true).toBe(true);
expect(true).toBe(true);
expect(true).toBe(true);
expect(true).toBe(true);
*
expect(true).not.toBe(true);
expect(true).not.toBe(true);
expect(true).not.toBe(true);
expect(true).not.toBe(true);
expect(true).not.toBe(true);
*
expect(true).toBe(true);
expect(a).not.toBe(null);
expect(a).toEqual(12);
expect(message).toMatch(/bar/);
expect(message).toMatch("bar");
expect(message).not.toMatch(/quux/);
expect(a.foo).toBeDefined();
expect(a.bar).not.toBeDefined();
*
NodeJS - CLI
In the Browser
*
*MxUnit was the standard
*TestBox is the new standard
*Other options
*
TestBox is a next generation testing
framework for ColdFusion (CFML) that is
based on BDD (Behavior Driven
Development) for providing a clean obvious
syntax for writing tests.
It contains not only a testing framework,
runner, assertions and expectations library
but also ships with MockBox, A Mocking &
Stubbing Framework,.
It also supports xUnit style of testing and
MXUnit compatibilities.
*
function testHelloWorld(){
$assert.includes( helloWorld(), ”world" );
}
*
describe("Hello world function", function() {
it(”contains the word world", function() {
expect(helloWorld()).toContain("world");
});
});
*
feature( "Box Size", function(){
describe( "In order to know what size box I need
As a distribution manager
I want to know the volume of the box", function(){
scenario( "Get box volume", function(){
given( "I have entered a width of 20
And a height of 30
And a depth of 40", function(){
when( "I run the calculation", function(){
then( "the result should be 24000", function(){
// call the method with the arguments and test the outcome
expect( myObject.myFunction(20,30,40) ).toBe( 24000 );
});
});
*
* There are a few choices
*
* Jasmine, Mocha and QUnit
*
*Jasmine comes ready to go out of the box
*Fluent Syntax – BDD Style
*Includes lots of matchers
*Has spies included
*Very popular, lots of support
*Angular uses Jasmine with Karma (CLI)
*Headless running and plays well with CI
servers
*
*Async testing in 1.3 can be a
headache
*Async testing in 2.0 is hard to find
blog posts on (I need to write one)
*Expects *spec.js suffix for test files
*This can be modified depending on
how you are running the tests
*
describe("Hello world function", function() {
it(”contains the word world", function() {
expect(helloWorld()).toContain("world");
});
});
*
*Simple Setup
*Simple Async testing
*Works great with other Assertion
libraries like Chai ( not included )
*Solid Support with CI Servers, with
Plugins for others
*Opinion says Mocha blazing the trail for
new features
*
*Requires other Libraries for key features
*No Assertion Library included
*No Mocking / Spied included
*Need to create the runner manually
*Newer to the game so not as popular or
supported as others but gaining traction.
*
var expect = require('chai').expect;
describe(’Hello World Function', function(){
it('should contain the word world', function(){
expect(helloWorld()).to.contain(’world');
})
})
*
*The oldest of the main testing frameworks
*Is popular due to use in jQuery and age
*Ember’s default Unit testing Framework
*
*Development slowed down since
2013 (but still under development)
*Syntax – No BDD style
*Assertion libraries – limited
matchers
*
QUnit.test( "ok test", function( assert ) {
assert.ok( true, "true succeeds" );
assert.ok( "non-empty", "non-empty string succeeds"
);
assert.ok( false, "false fails" );
assert.ok( 0, "0 fails" );
assert.ok( NaN, "NaN fails" );
assert.ok( "", "empty string fails" );
assert.ok( null, "null fails" );
assert.ok( undefined, "undefined fails" );
});
*
Photo Credit – Kombination
http://www.kombination.co.za/wp-content/uploads/2012/10/baby_w_spaghetti_mess_4987941.jpg
*
*
*
*Things to refactor to make your code testable
*Code should not be one big chunk of
Javascript in onReady()
*Deep nested callbacks & Anon functions
cannot easily be singled out and tested
*Remove Tight Coupling – DOM access for
example
*
*Lets look at some code
*This isn’t BEST PRACTICE, its BETTER
PRACTICE than you were doing
*Its not really refactoring if you don’t have
tests, its
“moving code and asking for trouble”
Kev McCabe
*
var personObjLit = {
ssn: ’xxxxxxxx',
age: '35',
name: 'Gavin Pickin',
getAge: function(){
return this.age;
},
getName: function() {
return this.name;
}
};
*
var personObjLit2 = function() {
ssn = ’xxxxxxx';
age = '35';
name = 'Gavin Pickin’;
return {
getAge: function(){
return age;
},
getName: function() {
return name;
}
};
};
*
*Using HTML Test Runners
*Keep a Browser open
*F5 refresh tests
*
*Run Jasmine – manual
*Run tests at the end of each section of work
*Run Grunt-Watch – automatic
*Runs Jasmine on every file change
*Grunt can run other tasks as well,
minification etc
*
*Browser Views
*Eclipse allows you to open files in
web view – uses HTML Runner
*Run Jasmine / Grunt / Karma in IDE
Console
*Fairly Easy to setup
*See Demo– Sublime Text 2 (if we have
time)
*
*Install / Run Jasmine Standalone for Browser
*Install / Run Jasmine with NodeJs
*Install / Run Jasmine with Grunt Watch
*Install / Run Testbox in Browser
*Install / Run Testbox with Grunt Watch
*Install / Run Grunt Watch inside Sublime Text 2
*
Download standalone package from Github (I have 2.1.3)
https://github.com/jasmine/jasmine/tree/master/dist
Unzip into your /tests folder
Run /tests/SpecRunner.html to see example tests
*
*
http://www.testableapi.local.com:8504/tests/SpecRunner.html
*
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Jasmine Spec Runner v2.1.3</title>
<link rel="shortcut icon" type="image/png" href="lib/jasmine-2.1.3/jasmine_favicon.png">
<link rel="stylesheet" href="lib/jasmine-2.1.3/jasmine.css”>
<script src="lib/jasmine-2.1.3/jasmine.js"></script>
<script src="lib/jasmine-2.1.3/jasmine-html.js"></script>
<script src="lib/jasmine-2.1.3/boot.js"></script>
<!-- include source files here... -->
<script src="../js/services/loginService.js"></script>
<!-- include spec files here... -->
<script src="spec/loginServiceSpec.js"></script>
</head>
<body>
</body>
</html>
*
Assuming you have NodeJs Installed… install Jasmine
$ npm install jasmine
[email protected] node_modules/jasmine
├── [email protected]
├── [email protected]
└── [email protected] ([email protected], [email protected])
*
Once Jasmine is installed in your project
$ Jasmine init
*
Edit Jasmine.json to update Locations for Spec Files and Helper Files
{
"spec_dir": "spec",
"spec_files": [
"**/*[sS]pec.js"
],
"helpers": [
"helpers/**/*.js"
]
}
*
$ Jasmine
Started
F
Failures:
1) A suite contains spec with an expectation
Message:
Expected true to be false.
Stack:
Error: Expected true to be false.
at Object.<anonymous>
(/Users/gavinpickin/Dropbox/Apps/testApp/www/spec/test_spec.js:3
:18)
1 spec, 1 failure
Finished in 0.009 seconds
*
*Jasmine-Node is great for Node
*Jasmine Node doesn’t have a headless browser
*Hard to test Browser code
*So what should I use?
*
* Install Grunt
npm install grunt
* Install Grunt – Jasmine
npm install grunt-contrib-jasmine
* Install Grunt – Watch
npm install grunt-contrib-watch
* Note: On Mac, I also needed to install Grunt CLI
npm install –g grunt-cli
*
// gruntfile.js - https://gist.github.com/gpickin/1e1e7902d1d3676d23c5
module.exports = function (grunt) {
grunt.initConfig({
pkg:
grunt.file.readJSON('node_modules/grunt/package.json'),
jasmine: {
all: {
src: ['js/*.js' ],
options: {
//'vendor': ['path/to/vendor/libs/*.js'],
'specs': ['specs/*.js' ], '--web-security':
false
}
}
*
// gruntfile.js part 2
watch: {
js: {
files: [
'js/*.js',
'specs/*.js',
],
tasks: ['jasmine:all']
}
}
});
*
// gruntfile.js part 3
grunt.loadNpmTasks('grunt-contrib-jasmine');
grunt.loadNpmTasks('grunt-contrib-watch');
};
*
describe("Forgotten Password Form", function() {
it("should warn you if the email is invalid before making Ajax Call",
function() {
expect( isEmailInputInvalid('') ).toBe(true);
expect( isEmailInputInvalid('dddddddddd') ).toBe(true);
expect( isEmailInputInvalid('dddddd@') ).toBe(true);
expect( isEmailInputInvalid('dddddd@ddddd') ).toBe(true);
expect( isEmailInputInvalid('dddddd@ddddddd.') ).toBe(true);
expect( isEmailInputInvalid('[email protected]') ).toBe(false);
});
});
*
describe("Login Form", function() {
it("should set status correct status message with successful Ajax
Response", function() {
spyOn( window, "setStatusMessage");
processLoginAjaxDone('{"RESULT":"200"}');
expect(setStatusMessage).toHaveBeenCalled();
expect(setStatusMessage).toHaveBeenCalledWith(
‘TARDIS Access Granted - Please wait for the Doctor to take you for
a spin');
});
});
*
describe("Login API", function() {
it("should return a failing Ajax Response", function() {
spyOn( window, "processLoginAjaxDone");
loginButtonEventHandlerProcess( '[email protected]',
'password');
expect(processLoginAjaxDone).toHaveBeenCalled();
expect(processLoginAjaxDone).toHaveBeenCalledWith(
‘{"RESULT":400}');
expect(processLoginAjaxFail).not.toHaveBeenCalled();
});
});
*
describe("Login API", function() {
it("should return a failing Ajax Response", function() {
spyOn( window, "processLoginAjaxDone");
loginButtonEventHandlerProcess( '[email protected]',
'password');
expect(processLoginAjaxDone).toHaveBeenCalled();
expect(processLoginAjaxDone).toHaveBeenCalledWith(
‘{"RESULT":400}');
expect(processLoginAjaxFail).not.toHaveBeenCalled();
});
});
*
*You want Unit Tests to test the unit and not it’s
dependencies
*You want Unit Tests to run quick
*You should mock the API in the Ajax call
*But we want to test the API
*So essentially, we’re writing an integration test.
*
describe("Login API", function() {
beforeEach(function( done ) {
spyOn( window, "processLoginAjaxDone").and.callFake(
function(){ done(); });
spyOn( window, "processLoginAjaxFail").and.callFake(
function(){ done(); });
loginButtonEventHandlerProcess('[email protected]', 'password');
});
it("should return a failing Ajax Response", function() { });
});
*
describe("Login API", function() {
beforeEach(function( done ) {
…
});
it("should return a failing Ajax Response", function() {
expect(processLoginAjaxDone).toHaveBeenCalled();
expect(processLoginAjaxDone).toHaveBeenCalledWith(
'{"RESULT":400}');
expect(processLoginAjaxFail).not.toHaveBeenCalled();
});
});
*
*
*
*Install Testbox – Thanks to Commandbox
* box install testbox
* Decide how you want to run Testbox
*
* <cfsetting showDebugOutput="false">
* <!--- Executes all tests in the 'specs' folder with simple reporter by
default --->
* <cfparam name="url.reporter"
* <cfparam name="url.directory"
* <cfparam name="url.recurse"
type="boolean">
* <cfparam name="url.bundles"
* <cfparam name="url.labels"
default="simple">
default="tests.specs">
default="true"
default="">
default="">
* <!--- Include the TestBox HTML Runner --->
* <cfinclude template="/testbox/system/runners/HTMLRunner.cfm" >
*
// tests/specs/CFCTest.cfc
component extends="testbox.system.BaseSpec" {
function run() {
it( "will error with incorrect login", function(){
var oTest = new cfcs.userServiceRemote();
expect( oTest.login( '[email protected]',
'topsecret').result ).toBe('400');
});
}
}
*
// tests/specs/APITest.cfc
component extends="testbox.system.BaseSpec" {
function run() {
describe("userService API Login", function(){
it( "will error with incorrect login", function(){
var email = "[email protected]";
var password = "topsecret”;
var result = "";
http
url="http://www.testableapi.local.com:8504/cfcs/userServiceRemote.cfc?metho
d=login&email=#email#&password=#password#" result="result”;
expect( DeserializeJSON(result.filecontent).result ).toBe('400');
});
});
}
}
*
*Install Testbox Runner – Thanks Sean Coyne
* npm install testbox-runner
* Install Grunt Shell
* npm install grunt-shell
* Add Grunt Configuration
*
*Install Testbox Runner – Thanks Sean Coyne
* npm install testbox-runner
* Install Grunt Shell
* npm install grunt-shell
* Add Grunt Configuration
*
module.exports = function (grunt) {
grunt.loadNpmTasks('grunt-shell');
grunt.initConfig({ … })
}
*
Watch: {
…
cfml: {
files: [ "cfcs/*.cfc"],
tasks: [ "testbox" ]
}
}
*
shell: {
testbox: {
command: "./node_modules/testboxrunner/index.js --colors --runner
http://www.testableapi.local.com:8504/tests/r
unner.cfm --directory /tests/specs --recurse
true”
}
}
*
grunt.registerTask("testbox", [ "shell:testbox" ]);
grunt.loadNpmTasks('grunt-contrib-jasmine');
grunt.loadNpmTasks('grunt-contrib-watch');
*
js: {
files: [
'js/*.js',
'specs/*.js',
"cfcs/*.cfc”
],
tasks: ['jasmine:all']
},
*
Jasmine
https://gist.github.com/gpickin/1e1e7902d1d3
676d23c5
Jasmine + Testbox
https://gist.github.com/gpickin/9fc82df3667ee
b63c7e7
*
*
* Testbox has several runners, you have seen the
HTML one, this Runner uses the JSON runner and
then formats it.
* http://www.testableapi.local.com:8504/tests/run
ner.cfm?reporter=JSON&directory=%2Ftests%2Fspec
s&recurse=true
*
* Install PackageControl into Sublime Text
* Install Grunt from PackageControl
* https://packagecontrol.io/packages/Grunt
* Update Grunt Sublime Settings for paths
{
"exec_args": { "path": "/bin:/usr/bin:/usr/local/bin”
}
*Then Command Shift P – grunt
}
*
*
* Any questions?
* Interesting Link:
https://medium.com/@Zyklus/beautiful-seamlessjavascript-testing-in-10-minutes-2a743637035b
*
FREE
ONLINE
https://www.ortussolutions.com/odw