Testing utilities for stubbing Program
This allows for testing assertions. It's fairly primitive, but will work for the time being. * src/test/README: Add file. * src/test/program/DummyClassifier.js: Add module. * src/test/program/Program.js: Add class. * src/test/program/util.js: Add module.master
parent
e610372c84
commit
5f36d9272f
|
@ -0,0 +1,10 @@
|
||||||
|
Liza Testing Library
|
||||||
|
====================
|
||||||
|
|
||||||
|
This namespace contains a library to aid in the testing of code that _makes
|
||||||
|
use of_ Liza. Especially due to legacy reasons, there are some parts of the
|
||||||
|
system that are quite difficult to mock and work with.
|
||||||
|
|
||||||
|
For test cases of Liza itself, see `/test' in the project root (sibling of
|
||||||
|
`/src').
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
/**
|
||||||
|
* Dummpy classifier for TestProgram
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = function() {};
|
||||||
|
module.exports.knownFields = {};
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
/**
|
||||||
|
* Mockable Program
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 LoVullo Associates, Inc.
|
||||||
|
*
|
||||||
|
* This file is part of liza.
|
||||||
|
*
|
||||||
|
* liza is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = program_path => require( program_path )
|
||||||
|
.extend(
|
||||||
|
{
|
||||||
|
classifier: __dirname + '/DummyClassifier',
|
||||||
|
} );
|
||||||
|
|
|
@ -0,0 +1,252 @@
|
||||||
|
/**
|
||||||
|
* Utility functions for Program testing
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 LoVullo Associates, Inc.
|
||||||
|
*
|
||||||
|
* This file is part of liza.
|
||||||
|
*
|
||||||
|
* liza is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
|
||||||
|
// N.B.: if the step titles change, these keys will change; we consider this
|
||||||
|
// to be acceptable because, if steps change, the tests will also likely
|
||||||
|
// change, and this is the only unique identifier we have (perhaps another
|
||||||
|
// can be added in the future that won't change)
|
||||||
|
exports.stepNameIdMap = Sut => Sut().steps.reduce(
|
||||||
|
( result, { title }, step_id ) =>
|
||||||
|
{
|
||||||
|
result[ title.replace( ' ', '_' ) ] = step_id;
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run tests against Program assertions
|
||||||
|
*
|
||||||
|
* Provided will be an expect-style testing framework and a Program
|
||||||
|
* SUT, along with a descriptor-providing callback `descf`. The callback
|
||||||
|
* will be invoked with certain useful information (like a step map) and is
|
||||||
|
* expected to return an array of test descriptors, which are of the format:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* { label<string>,
|
||||||
|
* event<string>,
|
||||||
|
* step_id<string>,
|
||||||
|
* data<Object>,
|
||||||
|
* cmatch<Object>,
|
||||||
|
* trigger<function(string, string, string, Array<string>)>,
|
||||||
|
* expected<Object> }.
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* `step_id` can be derived from the step map provided to `descf`. `data`
|
||||||
|
* is a key-value map of bucket data. `cmatch` is a key-value map of
|
||||||
|
* classification match data of the form:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* { any<boolean>,
|
||||||
|
* indexes<Array<number>> }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* `trigger` is an optional function to be invoked for any triggers that
|
||||||
|
* fire during assertion events. Its arguments, respectively, should be the
|
||||||
|
* event name; the name of the field on which the event was triggered; the
|
||||||
|
* trigger value; and any indexes associated with the trigger.
|
||||||
|
*
|
||||||
|
* `expected` is the expected failure result of the assertion, and is a
|
||||||
|
* key-value map of the field id on which the failure occurred to an array
|
||||||
|
* of string indexes of the failures (strings for legacy reasons; may change
|
||||||
|
* in the future).
|
||||||
|
*
|
||||||
|
* Each test will trigger the event `event` (e.g. `submit`, `change`) only
|
||||||
|
* for the given step `step_id`.
|
||||||
|
*
|
||||||
|
* N.B.: `expect` is required rather than chai being explicitly required in
|
||||||
|
* this module to prevent a dependency situation whereby chai is pulled in
|
||||||
|
* when parsing the dependency graph of /src.
|
||||||
|
*
|
||||||
|
* @param {*} expect expect-style BDD interface
|
||||||
|
* @param {Program} Sut Program SUT
|
||||||
|
* @param {function(Object<steps>)} descf descriptor-providing callback
|
||||||
|
*
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
exports.testAssertions = ( expect, Sut, descf ) =>
|
||||||
|
{
|
||||||
|
const StubSut = exports.stubProgram( Sut );
|
||||||
|
|
||||||
|
// test descriptors
|
||||||
|
const descs = descf( {
|
||||||
|
steps: this.stepNameIdMap( StubSut )
|
||||||
|
} );
|
||||||
|
|
||||||
|
if ( !Array.isArray( descs ) )
|
||||||
|
{
|
||||||
|
throw TypeError( "Expected array of test descriptors" );
|
||||||
|
}
|
||||||
|
|
||||||
|
descs.forEach( ( {
|
||||||
|
expected,
|
||||||
|
label,
|
||||||
|
event: event_id,
|
||||||
|
data: given_data,
|
||||||
|
step_id,
|
||||||
|
cmatch = {}
|
||||||
|
} ) =>
|
||||||
|
{
|
||||||
|
it( label, () =>
|
||||||
|
{
|
||||||
|
const sut = StubSut();
|
||||||
|
|
||||||
|
const result = exports.handleEvent( sut, event_id, {
|
||||||
|
step_id: step_id,
|
||||||
|
bucket: exports.stubBucket( sut, given_data ),
|
||||||
|
cmatch: exports.stubCmatch( cmatch ),
|
||||||
|
} );
|
||||||
|
|
||||||
|
for ( let name in expected )
|
||||||
|
{
|
||||||
|
expect( result[ name ].map( c => ''+c.getField().getIndex() ) )
|
||||||
|
.to.deep.equal( expected[ name ] );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produce a minimal bucket-like object suitable for Program assertions
|
||||||
|
*
|
||||||
|
* This is _not_ a general-purpose bucket mock; it supports only
|
||||||
|
* `#getDataByName`.
|
||||||
|
*
|
||||||
|
* @param {Program} sut program under test
|
||||||
|
* @param {Object} given_data stub bucket data (key/value)
|
||||||
|
*
|
||||||
|
* @return {Object} bucket-like object
|
||||||
|
*/
|
||||||
|
exports.stubBucket = ( sut, given_data ) =>
|
||||||
|
{
|
||||||
|
const { defaults } = sut;
|
||||||
|
|
||||||
|
// provide default bucket data so tests don't blow up
|
||||||
|
const base_data = Object.keys( defaults )
|
||||||
|
.map( key => [ defaults[ key ] ] );
|
||||||
|
|
||||||
|
// the given_data will need to be converted into property descriptors
|
||||||
|
const data = Object.create(
|
||||||
|
base_data,
|
||||||
|
Object.keys( given_data ).reduce( ( bdata, name ) => {
|
||||||
|
bdata[ name ] = { value: given_data[ name ] };
|
||||||
|
return bdata;
|
||||||
|
}, {} )
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
getDataByName( name )
|
||||||
|
{
|
||||||
|
return data[ name ] || [];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trigger proper program event and return assertion results
|
||||||
|
*
|
||||||
|
* The final argument is a descriptor that must at least contain a step id,
|
||||||
|
* and may optionally contain bucket and cmatch data, and a trigger callback
|
||||||
|
* function. This is also the test descriptor format.
|
||||||
|
*
|
||||||
|
* @param {Program} sut program under test
|
||||||
|
* @param {string} event_id event id
|
||||||
|
* @param {Object} data descriptor
|
||||||
|
*
|
||||||
|
* @return {?Object} assertion results or null if no failures
|
||||||
|
*/
|
||||||
|
exports.handleEvent = (
|
||||||
|
sut,
|
||||||
|
event_id,
|
||||||
|
{ step_id, bucket = {}, cmatch = {}, trigger = () => {} }
|
||||||
|
) =>
|
||||||
|
{
|
||||||
|
if ( typeof step_id !== 'number' )
|
||||||
|
{
|
||||||
|
throw TypeError( `Invalid step_id '${step_id}'`)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ( event_id )
|
||||||
|
{
|
||||||
|
case 'submit':
|
||||||
|
return sut.submit( step_id, bucket, cmatch, trigger );
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw Error( `Unknown event: ${event_id}` );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produce a stub cmatch object suitable for Program assertions
|
||||||
|
*
|
||||||
|
* The `cmatch` array should contain an array of numbers with a `1`
|
||||||
|
* representing a match at that respective index and a `0` representing no
|
||||||
|
* match. This function will generate the appropriate cmatch object.
|
||||||
|
*
|
||||||
|
* This is _not_ a general-purpose cmatch mock.
|
||||||
|
*
|
||||||
|
* @param {Array<number>} cmatch key/value map of class to matching indexes
|
||||||
|
*
|
||||||
|
* @return {Object} stub cmatch
|
||||||
|
*/
|
||||||
|
exports.stubCmatch = ( cmatch_dfn ) =>
|
||||||
|
{
|
||||||
|
const __classes = Object.keys( cmatch_dfn )
|
||||||
|
.reduce( ( classes, name ) =>
|
||||||
|
{
|
||||||
|
classes[ name ] = {
|
||||||
|
is: cmatch_dfn[ name ].some( matched => matched ),
|
||||||
|
indexes: cmatch_dfn[ name ].map( matched => +matched ),
|
||||||
|
};
|
||||||
|
|
||||||
|
return classes;
|
||||||
|
}, {} );
|
||||||
|
|
||||||
|
const cmatch = Object.keys( cmatch_dfn )
|
||||||
|
.reduce( ( classes, name ) =>
|
||||||
|
{
|
||||||
|
classes[ name ] = {
|
||||||
|
any: cmatch_dfn[ name ].some( i => +i === 1 ),
|
||||||
|
all: cmatch_dfn[ name ].every( i => +i === 1 ),
|
||||||
|
indexes: cmatch_dfn[ name ],
|
||||||
|
};
|
||||||
|
|
||||||
|
return classes;
|
||||||
|
}, {} );
|
||||||
|
|
||||||
|
cmatch.__classes = __classes;
|
||||||
|
|
||||||
|
return cmatch;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
exports.stubProgram = Program => Program.extend(
|
||||||
|
{
|
||||||
|
classifier: __dirname + '/DummyClassifier',
|
||||||
|
} );
|
Loading…
Reference in New Issue