Background
QBO 3 uses the Jasmine Behavior Driven Development (BDD) framework for unit testing functionality. As project managers develop features, they shall draft a test spec following BDD guidelines.
For example, the notification behavior feature includes the following test spec:
notifications can be inserted (insert a message with priority = 1)
the notification badge indicates 1 notifications pending
clicking on the notification badge shows the inserted notification
notifications can be deleted (delete the message)
the notification badge indicates 0 notifications pending
Existing test specs can be viewed from the standard QBO 3 main menu's Design > Specifications.
To implement a test spec, the following steps should be taken:
In the appropriate web project, ensure a /Scripts/{Namespace}/Specs folder exists
Add a javascript file to the /Scripts/{Namespace}/Specs folder named Spec.{Feature}.js (e.g. Spec.Notifications.js)
Add the javascript code required to implement the test spec defined in the feature
See existing specs (such as qbo.ContactWeb > Scripts > Specs > Contact > Spec.Contact.js) for examples.
Useful Specs
The following specs can quickly tell you if a QBO system is installed and configured properly:
Application > Configuration: tests all classes in web.config for a matching js class and table structure
Attachment > File Objects: tests all configured file objects to ensure they are properly configured
Security > Login: ensure new user accounts can be created and password resets work
Decision > Smart Worklists: ensures smart worklists are properly configured, including use of matricies
Sample Specification
The Security > Login spec performs the following tests:
creates a new user
issues a password reset
allows resetting of a password via a secure link
does not allow short passwords
does not allow weak passwords
does not allow key words
does allow strong passwords
cleans up the data created
In order to perform these tests, the script will invoke the Login, Person, and SecurityAccess modules. The javascript code to wire that is:
var login = qbo3.getJasmineObject('LoginObject', 'results');
var person = qbo3.getJasmineObject('PersonObject', 'results');
var access = qbo3.getJasmineObject('SecurityAccessObject', 'results');
The qbo3.getJasmineObject is a helper method (found in /Templates/Specs/qbo.SpecHelpers.js). It instantiates a qbo-based javascript class (e.g. new qbo3.PersonObject()), and automatically wires the class to be used in a spec. This includes tying into event handlers for JSON responses and failures.
To test creation of a person:
it('New users can be created', function () {
person.save({
'Person': 'Spec Test User - ' + person.spec.uniqueID,
'Email': testEmail,
'FirstName': person.spec.uniqueID,
'LastName': 'SpecUser',
'Active': 1
});
expect(person.properties.PersonID).toBeDefined();
});
In this case, we simply invoke the Person.ashx/Save to create a new user. Note that the Person.Person includes a unique ID, so we can later search for that unique name if we need to. If we get a PersonID in the response, we consider the test a success.
To test the password reset request functionality:
it('Can request a password reset', function () {
login.resetPassword({ 'Person': 'Spec Test User - ' + person.spec.uniqueID });
expect(login).toHaveElement('!body div.alert-success');
});
In this case, success is defined as receiving an alert-success message in the UI.
The next test checks to see if a one-time access (SecurityAccess) row was properly created for the new user:
it('allows resetting of a password via a secure link', function () {
access.invokeJson('Search', {
'PersonID': person.properties.PersonID
});
expect(access.spec.json.SecurityAccessCollection.SecurityAccessItem[0].SecurityAccessID).toBeDefined();
login.invoke('Impersonate', { 'PersonID': person.properties.PersonID });
expect(login.spec.errors).toEqual(0);
});
Note the use of Impersonate here; this is incredibly useful for testing various security scenarios. Impersonation is logged, and can be disabled site-wide for PROD sites. However, it's very useful for testing.
The next 4 tests simply call Login.ashx/SetPassword, 3 with invalid passwords, and the 4th with a valid password:
it('does not allow short passwords', function () {
login.invokeJson('SetPassword', { 'Password': 'Easy' });
expect(login.spec.json.success).toBeFalsy();
});
it('does not allow weak passwords', function () {
login.invokeJson('SetPassword', { 'Password': 'EasyPeasy' });
expect(login.spec.json.success).toBeFalsy();
});
it('does not allow key words', function () {
login.invokeJson('SetPassword', { 'Password': 'QuandisRocks!' });
expect(login.spec.json.success).toBeFalsy();
});
it('does allow strong passwords', function () {
login.invokeJson('SetPassword', { 'Password': login.spec.uniqueID + 'A!' });
expect(login.spec.json.success).toBeTruthy();
});
Lastly, we clean up the data recently inserted:
it('Can delete users', function () {
login.invoke('UnImpersonate');
expect(login.spec.errors).toEqual(0);
person.invoke('Delete', { 'PersonID': person.properties.PersonID });
});
Helper Methods
When calling qbo3.getJasmineObject, the object returns has a 'spec' object attached:
instance.spec = {
uniqueID: String.uniqueID(), // used to track data so you know where it came from
idList: new Array(), // keep a list if IDs inserted, so you can delete them as part of cleanup
errors: 0, // each time an error is caught, this is incremented
data: null, // stores XML documents received when calling invokeXml
json: null // stores JSON objects received when calling invokeJson
}
The spec.json is probably the most useful feature; it allows you to easily process any JSON responses from the server very quickly.
Additional helper methods in the qbo3.jasmine object include:
hasAttribute(field, attribute): returns true if a field exists has an attribute or class (e.g. 'readonly' or 'data-behavior')
isReadOnly(field): calls hasAttribute(field, 'readonly')
isRequired(field): calls hasAttribute(field, 'required')
getJsonItems(json, column, value): parses a JSON array for items with a column (property) matching value
getJsonItem(json, column, value): returns the first value found by getJsonItems()
Logging
Spec test results log to the Message table:
Spec test results can be identified by a 'SpecLog' value in the MessageType column
The BodyText column holds the folder/name of the spec test file that was processed
The Object column holds the 'suite' name, or grouping, for the specification
The Message column holds the 'it statement' or description of the specification
The Status column holds the pass/fail value for the specification
Pattern for querying test spec results:
--pattern:
--SELECT * FROM Message WHERE MessageType = 'SPECLOG'
--AND OBJECT = [SUITE NAME]
--AND MESSAGE = [IT STATEMENT]
--examine STATUS column for PASS/FAIL results
Sample queries for test spec results:
--examples of pulling data related to Decision/Spec.Decision.js:
--get all messages relevant to suite 'Workflow (Decision Processing)', most recent first
SELECT * FROM MESSAGE WHERE MESSAGETYPE = 'SPECLOG' AND OBJECT = 'WORKFLOW (DECISION PROCESSING)'order by MessageID desc
--get all messages relevant to SUITE = 'Workflow (Decision Processing)' and It Statement = 'can create an ImportForm step', most recent first
SELECT * FROM MESSAGE WHERE MESSAGETYPE = 'SPECLOG' AND OBJECT = 'WORKFLOW (DECISION PROCESSING)'AND Message = 'CAN CREATE AN IMPORTFORM STEP'order by MessageID desc
Road Map
Future versions of QBO3 will support the following spec enhancements:
migration of all specs into IFileObject storage, enabling power users to design specs and distribution across load balanced web servers
front-end GUI for editing specs, including use of CodeMirror for javascript coding
front-end creation of "spec pacakages": a list of specs that are commonly run together
packages can be stored in ConfigurationEntry, and contains ConfigurationXml that simply includes all the test specs to run as part of the package
extension of a Jasmine reporter to log spec results to a server-side logging sink (e.g. Message table)