Skip to content

Instantly share code, notes, and snippets.

@adamcameron
Created December 1, 2013 14:54
Show Gist options
  • Save adamcameron/7734936 to your computer and use it in GitHub Desktop.
Save adamcameron/7734936 to your computer and use it in GitHub Desktop.
Code for article "Unit Testing / TDD - passing data to on() and trigger()"
// CustomAssertions.cfc
component {
public void function assertStructKeysCorrect(required string keys, required struct struct, string message){
var assertionArgs = {
expected = listSort(keys, "textnocase"),
actual = listSort(structKeyList(struct), "textnocase")
};
if (structKeyExists(arguments, "message")){
assertionArgs.message = message;
}
assertEquals(argumentCollection=assertionArgs);
}
}
<cfscript>
function createEventObject(){
var eventContainer = {};
return {
on = function(required string event, required function handler, struct data={}){
if (!structKeyExists(eventContainer, event)){
eventContainer[event] = [];
}
arrayAppend(eventContainer[event], arguments);
},
trigger = function(required string event, struct additionalParameters={}){
if (structKeyExists(eventContainer, event)){
for (eventEntry in eventContainer[event]){
var eventObj = {
event = event,
data = eventEntry.data
};
eventEntry.handler(event=eventObj, argumentCollection=additionalParameters);
}
}
}
};
};
</cfscript>
// TestBase.cfc
component extends="mxunit.framework.TestCase" {
public void function beforeTests(){
addAssertDecorator("CustomAssertions");
include "./function.cfm";
}
public void function setup(){
variables.eventObject = createEventObject();
}
}
// TestCreateEventObject.cfc
component extends="TestBase" {
public void function testBaseline(){
createEventObject();
// it doesn't error. That's it
}
public void function testReturnValues(){
assertIsStruct(variables.eventObject, "Returned value should be a struct");
assertStructKeysCorrect(
"on,trigger",
variables.eventObject,
"Incorrect keys returned in eventObject"
);
assertTrue(
isClosure(variables.eventObject.on),
"The returned on() value should be a function"
);
assertTrue(
isClosure(variables.eventObject.trigger),
"The returned trigger() value should be a function"
);
}
public void function testOnRequiresEventArg() {
var failMsg = "on() should require an EVENT argument";
try {
variables.eventObject.on(handler=function(){});
fail(failMsg);
} catch (any e){
// can't catch this coherently by exception type as CF and Railo return completely different exceptions here
assertTrue(
findNoCase("event", e.message) && findNoCase("parameter", e.message),
failMsg
);
}
}
public void function testOnRequiresHandlerArg() {
var failMsg = "on() should require an HANDLER argument";
try {
variables.eventObject.on(event="TestEvent");
fail(failMsg);
} catch (any e){
assertTrue(
findNoCase("handler", e.message) && findNoCase("parameter", e.message),
failMsg
);
}
}
public void function testTriggerRequiresEventArg() {
var failMsg = "trigger() should require an EVENT argument";
try {
variables.eventObject.trigger();
fail(failMsg);
} catch (any e){
assertTrue(
findNoCase("event", e.message) && findNoCase("parameter", e.message),
failMsg
);
}
}
}
// TestOn.cfc
component extends="TestBase" {
public void function testOnStoresHandler() {
structDelete(variables, "testOnStoresHandlerResponse"); // ensure it doesn't exist
var handlerResponse = createUuid();
variables.eventObject.on("TestEvent", function(){
variables.testOnStoresHandlerResponse = handlerResponse;
});
variables.eventObject.trigger("TestEvent");
assertTrue(
structKeyExists(variables, "testOnStoresHandlerResponse"),
"testOnStoresHandlerResponse should have been created"
);
assertEquals(
handlerResponse,
variables.testOnStoresHandlerResponse,
"testOnStoresHandlerResponse set incorrectly"
);
}
public void function testOnStoresMultipleEvents() {
structDelete(variables, "testOnStoresMultipleEvents1"); // ensure it doesn't exist
structDelete(variables, "testOnStoresMultipleEvents2"); // ensure it doesn't exist
var handlerResponse1 = createUuid();
var handlerResponse2 = createUuid();
variables.eventObject.on("TestEvent1", function(){
variables.testOnStoresMultipleEvents1 = handlerResponse1;
});
variables.eventObject.on("TestEvent2", function(){
variables.testOnStoresMultipleEvents2 = handlerResponse2;
});
variables.eventObject.trigger("TestEvent1");
assertTrue(
structKeyExists(variables, "testOnStoresMultipleEvents1"),
"testOnStoresMultipleEvents1 should have been created"
);
assertEquals(
handlerResponse1,
variables.testOnStoresMultipleEvents1,
"testOnStoresMultipleEvents1 set incorrectly"
);
variables.eventObject.trigger("TestEvent2");
assertTrue(
structKeyExists(variables, "testOnStoresMultipleEvents2"),
"testOnStoresMultipleEvents2 should have been created"
);
assertEquals(
handlerResponse2,
variables.testOnStoresMultipleEvents2,
"testOnStoresMultipleEvents2 set incorrectly"
);
}
public void function testOnStoresMultipleHandlers() {
structDelete(variables, "TestOnStoresMultipleHandlers1"); // ensure it doesn't exist
structDelete(variables, "TestOnStoresMultipleHandlers2"); // ensure it doesn't exist
var handlerResponse1 = createUuid();
var handlerResponse2 = createUuid();
variables.eventObject.on("TestEvent", function(){
variables.testOnStoresMultipleHandlers1 = handlerResponse1;
});
variables.eventObject.on("TestEvent", function(){
variables.testOnStoresMultipleHandlers2 = handlerResponse2;
});
variables.eventObject.trigger("TestEvent");
assertTrue(
structKeyExists(variables, "testOnStoresMultipleHandlers1"),
"testOnStoresMultipleHandlers1 should have been created"
);
assertEquals(
handlerResponse1,
variables.testOnStoresMultipleHandlers1,
"testOnStoresMultipleHandlers1 set incorrectly"
);
assertTrue(
structKeyExists(variables, "testOnStoresMultipleHandlers2"),
"testOnStoresMultipleHandlers2 should have been created"
);
assertEquals(
handlerResponse2,
variables.testOnStoresMultipleHandlers2,
"testOnStoresMultipleHandlers2 set incorrectly"
);
}
}
// TestTrigger.cfc
component extends="TestBase" {
// currently trigger() tests are actuaqlly performed as part of the on() tests.
public void function beforeTests(){
super.beforeTests();
testOn = new TestOn();
testOn.beforeTests();
}
public void function setup(){
super.setup();
testOn.setup();
}
// TODO: find a way to decouple these tests
public void function testTriggerCalledStoredHandler() {
testOn.testOnStoresHandler();
}
public void function testTriggerCalledMultipleStoredEvents() {
testOn.testOnStoresMultipleEvents();
}
public void function testTriggerNonExistentEvent() {
variables.eventObject.trigger("EventWithNoHandlers");
// the test is that it doesn't error
}
public void function testTriggerCalledMultipleStoredHandlers() {
testOn.testOnStoresMultipleHandlers();
}
public void function testTriggerPassesEventObjToHandler() {
variables.eventObject.on("TestEvent", function(){
if (!structKeyExists(arguments, "event")){
throw(type="MissingArgumentException", message="Event object not passed to handler");
}
});
try {
variables.eventObject.trigger("TestEvent");
}
catch (MissingArgumentException e){
fail(e.message);
}
}
public void function testTriggerPassesEventInEventObjToHandler() {
variables.eventObject.on("TestEvent", handlerForTestTriggerPassesEventInEventObjToHandler);
try {
variables.eventObject.trigger("TestEvent");
}
catch (MissingEventException e){
fail(e.message);
}
}
public void function testTriggerPassesDefaultDataInEventObjToHandler() {
variables.eventObject.on("TestEvent", function(){
if (!structKeyExists(arguments.event, "data")){
throw(type="MissingDataException", message="Data not passed to handler");
}
});
try {
variables.eventObject.trigger("TestEvent");
}
catch (MissingDatatException e){
fail(e.message);
}
}
public void function testTriggerPassesSpecificDataInEventObjToHandler() {
var testData = {key="value"};
variables.eventObject.on(
"TestEvent",
function(){
if (!structKeyExists(arguments.event, "data")){
throw(type="MissingDataException", message="Data not passed to handler");
}
if (!structKeyExists(arguments.event.data, "key")){
throw(type="InvalidDataException", message="key not found in data");
}
if (arguments.event.data.key != "value"){
throw(type="InvalidDataException", message="key has incorrect value");
}
},
testData
);
try {
variables.eventObject.trigger("TestEvent");
}
catch (MissingArgumentException e){
fail(e.message);
}
}
public void function testTriggerDefaultsAdditionalParameters() {
variables.eventObject.on(
"TestEvent",
function(){
if (structKeyList(arguments) != "event"){
throw(type="UnexpectedArgumentsException", message="trigger() passed unexpected arguments");
}
}
);
try {
variables.eventObject.trigger("TestEvent");
}
catch (UnexpectedArgumentsException e){
fail(e.message);
}
}
public void function testTriggerPassesAdditionalParameters() {
var additionalParams ={
triggerParam1 = "triggerParam1Value",
triggerParam2 = "triggerParam2Value"
};
variables.eventObject.on(
"TestEvent",
function(){
var argNames = structKeyList(arguments);
if (listSort(argNames, "textNoCase") != "event,triggerParam1,triggerParam2"){
throw(type="UnexpectedArgumentsException", message="trigger() passed unexpected arguments: #argNames#");
}
}
);
try {
variables.eventObject.trigger("TestEvent", additionalParams);
}
catch (UnexpectedArgumentsException e){
fail(e.message);
}
}
private void function handlerForTestTriggerPassesEventInEventObjToHandler(required struct event){
if (!structKeyExists(arguments.event, "event")){
throw(type="MissingEventException", message="Event name not passed to handler");
}
if (arguments.event.event != "TestEvent"){
throw(type="InvalidEventException", message="Event object did not contain correct event name");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment