3 WAYS TO TEST YOUR COLDFUSION API -

ortussolutions 1,042 views 86 slides Jul 24, 2017
Slide 1
Slide 1 of 86
Slide 1
1
Slide 2
2
Slide 3
3
Slide 4
4
Slide 5
5
Slide 6
6
Slide 7
7
Slide 8
8
Slide 9
9
Slide 10
10
Slide 11
11
Slide 12
12
Slide 13
13
Slide 14
14
Slide 15
15
Slide 16
16
Slide 17
17
Slide 18
18
Slide 19
19
Slide 20
20
Slide 21
21
Slide 22
22
Slide 23
23
Slide 24
24
Slide 25
25
Slide 26
26
Slide 27
27
Slide 28
28
Slide 29
29
Slide 30
30
Slide 31
31
Slide 32
32
Slide 33
33
Slide 34
34
Slide 35
35
Slide 36
36
Slide 37
37
Slide 38
38
Slide 39
39
Slide 40
40
Slide 41
41
Slide 42
42
Slide 43
43
Slide 44
44
Slide 45
45
Slide 46
46
Slide 47
47
Slide 48
48
Slide 49
49
Slide 50
50
Slide 51
51
Slide 52
52
Slide 53
53
Slide 54
54
Slide 55
55
Slide 56
56
Slide 57
57
Slide 58
58
Slide 59
59
Slide 60
60
Slide 61
61
Slide 62
62
Slide 63
63
Slide 64
64
Slide 65
65
Slide 66
66
Slide 67
67
Slide 68
68
Slide 69
69
Slide 70
70
Slide 71
71
Slide 72
72
Slide 73
73
Slide 74
74
Slide 75
75
Slide 76
76
Slide 77
77
Slide 78
78
Slide 79
79
Slide 80
80
Slide 81
81
Slide 82
82
Slide 83
83
Slide 84
84
Slide 85
85
Slide 86
86

About This Presentation

Most projects in CF now involve creating some type of consumable CFC Endpoint or API Service... do you Unit test your API, do you use Integration Tests on your API? How many ways do you test your API? Not all tests are created equal.

We build our CFCs and CF API to be consumed with CF Apps, Mobile ...


Slide Content

3 WAYS TO TEST YOUR
COLDFUSION API
Gavin Pickin
cf.Objective() 2017

Agenda
●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

Who am I?
Gavin Pickin – developing Web Apps since late 90s
●Software Consultant for 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
Let’s get on with the show.

State of the Room
●A few questions for you guys

●If you have arms, use them.

APIs in CFML
Most CF Apps are moving towards providing an API for multiple consumers
CF has many REST API Solutions and even more with CF 2016
●Built in CF
●Built in Railo/Lucee
●Coldbox API
●Taffy

Ways to Test your Code


●Click around in the browser yourself

●Setup Selenium / Web Driver to
click around for you

●Structured Programmatic Tests

Types of Testing
●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 Testing
●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
“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

Unit Testing can...
●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

Styles – TDD vs BDD


●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

Styles - TDD vs BDD
●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

TDD Example
Test( ‘Email address must not be blank’, function(){
notEqual(email, “”, "failed");
});

BDD Example
Describe( ‘Email Address’, function(){
It(‘should not be blank’, function(){
expect(email).not.toBe(“”);
});
});

Matchers
expect(true).toBe(true);
expect(true).toBe(true);
expect(true).toBe(true);
expect(true).toBe(true);

Matchers
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);

Matcher Samples
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();

Different Testing
Environments?

NodeJS - CLI In the Browser

CF Testing Tools
*MxUnit was the standard

*TestBox is the new standard
Other options

TestBox
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.

TestBox TDD Example
function testHelloWorld(){
          $assert.includes( helloWorld(), ”world" );
     }

TestBox BDD Example
describe("Hello world function", function() {
it(”contains the word world", function() {
expect(helloWorld()).toContain("world");
});
});

TestBox New BDD Example
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 );
                      });
                 });
            });
        });
    });
});

JS Testing Tools
*There are a few choices

Main JS Testing Players
Jasmine, Mocha and QUnit

Jasmine
*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

Jasmine - Cons
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

Jasmine – Sample Test

describe("Hello world function", function() {
it(”contains the word world", function() {
expect(helloWorld()).toContain("world");
});
});

Mocha
*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

Mocha - Cons
*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.

Mocha – BDD Sample Test
var expect = require('chai').expect;

describe(’Hello World Function', function(){
it('should contain the word world', function(){
expect(helloWorld()).to.contain(’world');
})
})

QUnit

*The oldest of the main testing frameworks

*Is popular due to use in jQuery and age

*Ember’s default Unit testing Framework

QUnit - Cons

*Development slowed down since
2013 (but still under development)
*Syntax – No BDD style
*Assertion libraries – limited matchers

QUnit – Sample Test
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" );
});

Spaghetti Javascript
Photo Credit – Kombination
http://www.kombination.co.za/wp-content/uploads/2012/10/baby_w_spaghetti_mess_4987941.jpg

Spaghetti Javascript Example

Spaghetti Javascript Example

Refactoring Spaghetti
*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

Refactoring Spaghetti
*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

Object Literals
var personObjLit = {
ssn: ’xxxxxxxx',
age: '35',
name: 'Gavin Pickin',
getAge: function(){
return this.age;
},
getName: function() {
return this.name;
}
};

Module Pattern
var personObjLit2 = function() {
ssn = ’xxxxxxx';
age = '35';
name = 'Gavin Pickin’;
return {
getAge: function(){
return age;
},
getName: function() {
return name;
}
};
};

Using Testing in your Workflow
*Using HTML Test Runners
*Keep a Browser open
*F5 refresh tests

Command Line 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

Testing in your IDE
*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)

Live Demo and Examples

*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

Install / Run Jasmine for In-Browser Testing
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

Standalone Jasmine

Installing Jasmine for in Browser Testing
http://www.testableapi.local.com:8504/tests/SpecRunner.html

SpecRunner Setup Jasmine Browser Test
<!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>

Installing Jasmine with NodeJS
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])

Installing Jasmine with NodeJS
Once Jasmine is installed in your project

$ Jasmine init

Installing Jasmine with NodeJS
Edit Jasmine.json to update Locations for Spec Files and Helper Files
{
"spec_dir": "spec",
"spec_files": [
"**/*[sS]pec.js"
],
"helpers": [
"helpers/**/*.js"
]
}

Running Jasmine Tests with NodeJS
$ 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:1
8)

1 spec, 1 failure
Finished in 0.009 seconds

Running Jasmine Tests with NodeJS
*Jasmine-Node is great for Node
*Jasmine Node doesn’t have a headless browser
*Hard to test Browser code

*So what should I use?

Installing Jasmine with Grunt Watcher
*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

Configuring Jasmine with Grunt Watcher
// 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
}
}
},

Configuring Jasmine with Grunt Watcher
// gruntfile.js part 2
watch: {
js: {
files: [
'js/*.js',
'specs/*.js',
],
tasks: ['jasmine:all']
}
}
});

Configuring Jasmine with Grunt Watcher
// gruntfile.js part 3

grunt.loadNpmTasks('grunt-contrib-jasmine');
grunt.loadNpmTasks('grunt-contrib-watch');
};

Example Jasmine Spec with Grunt Watcher

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);
});
});

Example Jasmine Spec with Grunt Watcher
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');
});
});

Example Jasmine Spec with Grunt Watcher
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();
});
});

Whats wrong with that?
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();
});
});

Unit Tests and Async Calls
*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.

How to wait for Async
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() { });
});

How to wait for Async
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();
});
});

Running Jasmine with Grunt Watcher

Running Jasmine with Grunt Watcher

Installing Testbox
*Install Testbox – Easy Thanks to Commandbox
*box install testbox

*Next, decide how you want to run Testbox

Create a runner.cfm
*<cfsetting showDebugOutput="false">
*<!--- Executes all tests in the 'specs' folder with simple reporter by
default --->
*<cfparam name="url.reporter" default="simple">
*<cfparam name="url.directory" default="tests.specs">
*<cfparam name="url.recurse" default="true"
type="boolean">
*<cfparam name="url.bundles" default="">
*<cfparam name="url.labels" default="">

*<!--- Include the TestBox HTML Runner --->
*<cfinclude template="/testbox/system/runners/HTMLRunner.cfm"
>

Create a Test Suite
// 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');
});
}
}

Create a 2
nd
Test Suite
// 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?method=log
in&email=#email#&password=#password#" result="result”;
expect( DeserializeJSON(result.filecontent).result ).toBe('400');
});
});
}
}

Running Testbox with runner.cfm

Running Testbox with Grunt Watch
*Install Testbox Runner – Thanks Sean Coyne
*npm install testbox-runner

*Install Grunt Shell
*npm install grunt-shell

*Add Grunt Configuration

Adding TextBox Config 1
module.exports = function (grunt) {
grunt.loadNpmTasks('grunt-shell');
grunt.initConfig({ … })
}

Adding TextBox Config 2
Watch: {

cfml: {
files: [ "cfcs/*.cfc"],
tasks: [ "testbox" ]
}
}

Adding TextBox Config 3
shell: {
testbox: {
command:
"./node_modules/testbox-runner/index.js
--colors --runner
http://www.testableapi.local.com:8504/tests/r
unner.cfm --directory /tests/specs --recurse
true”
}
}

Adding TextBox Config 4
grunt.registerTask("testbox", [ "shell:testbox" ]);
grunt.loadNpmTasks('grunt-contrib-jasmine');
grunt.loadNpmTasks('grunt-contrib-watch');

Adding TextBox Config 5
js: {
files: [
'js/*.js',
'specs/*.js',
"cfcs/*.cfc”
],
tasks: ['jasmine:all']
},

GruntFile.js Gists
Jasmine
https://gist.github.com/gpickin/1e1e7902d1d36
76d23c5

Jasmine + Testbox
https://gist.github.com/gpickin/9fc82df3667ee
b63c7e7

Testbox output with Grunt

Testbox Runner JSON
*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/runner.cfm?rep
orter=JSON&directory=%2Ftests%2Fspecs&recurse=true

Running in Sublime Text 2
*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

Running in Sublime Text 2