a
Mocking framework
The mocking framework can be used in any JavaScript testing framework.
To install:
If you want the test framework, install it globally too:
Mocking
Mocking a function
Partial mock
import a from 'a'; var original = function() { return 'realValue'; } var mock = a.mock(original); original = mock; mock.expect().return('fake'); mock(); //returns 'fake' mock(); //returns 'realValue'
Note: Consumers do not need to provide a thisArg. It is optional and only used to force a specific this when the original fallback is called (low-level partial mock usage).
Strict mock
import a from 'a'; var mock = a.mock(); mock.expect().return('fake'); mock(); //returns 'fake' mock(); //throws unexpected arguments
Expecting arguments
import a from 'a'; var mock = a.mock(); mock.expect('testValue1').return('fake1'); mock.expect('testValue2').return('fake2'); mock('testValue1'); //returns 'fake1' mock('testValue2'); //returns 'fake2' mock(); //throws unexpected arguments mock('foo'); //throws unexpected arguments
Expecting multiple arguments
import a from 'a'; var mock = a.mock(); mock.expect('firstArg1', 'secondArg1').return('fake1'); mock.expect('firstArg2', 'secondArg2').return('fake2'); mock('firstArg1', 'secondArg1'); //returns 'fake1' mock('firstArg2', 'secondArg2'); //returns 'fake2' mock('foo'); //throws unexpected arguments mock('foo', 'bar'); //throws unexpected arguments
Expecting array
import a from 'a'; var mock = a.mock(); mock.expect(['a','b']).return('fake1'); mock.expect(['a','b']).return('fake2'); mock.expect(['c','d').return('fake3'); mock(['a','b']); //returns 'fake1' mock(['a','b']); //returns 'fake2' mock(['c','d']); //returns 'fake3' mock(['a','b']); //throws unexpected arguments mock(['foo', 'bar']); //throws unexpected arguments
Expecting struct
import a from 'a'; var mock = a.mock(); var obj = {}; mock.expect({a : 1}).return('fake1'); mock.expect({a : 2}).return('fake2'); mock.expect({a : 2, b : {c : 'foo', d : ['me', 'too']}}).return('fake3'); mock.expect(obj).return('fake4'); mock.expect({}).return('will never happen'); mock({a : 'x'}); //throws unexpected arguments mock({a : 1}); //returns 'fake1' mock({a : 2}); //returns 'fake2' mock({a : 2, b : {c : 'foo', d : ['me', 'too']}}); //returns 'fake3' mock(obj); //returns 'fake4' mock({}); //throws unexpected arguments
Note: Struct matching is strict on leaf properties. All leaf property values must be equal to match, and an empty object does not match a non-empty expected struct.
Ignoring a single argument
import a from 'a'; var mock = a.mock(); mock.ignore().expect('foo').return('fake1'); mock('ignore me', 'foo'); //returns 'fake1' mock(); //throws unexpected arguments
Ignoring all arguments
import a from 'a'; var mock = a.mock(); mock.ignoreAll().return('fake1'); //same as expectAnything mock('someRandomValue', 'whatever'); //returns 'fake1' mock(); //throws unexpected arguments
Repeats
import a from 'a'; var mock = a.mock(); mock.expect().return('fake').repeat(2); mock(); //returns 'fake' mock(); //returns 'fake' mock(); //throws unexpected arguments
Infinite repeats
import a from 'a'; var mock = a.mock(); mock.expect().return('fake').repeatAny(); mock(); //returns 'fake' mock(); //returns 'fake' mock(); //returns 'fake'...
Throwing exceptions
import a from 'a'; var mock = a.mock(); var error = new Error('invalid operation'); mock.expect().throw(error); mock.expect().return('fake'); mock(); //throws error mock(); //returns 'fake'
Intercepting
import a from 'a'; var mock = a.mock(); mock.expect('testValue').whenCalled(onCalled).return('fake1'); function onCalled(arg) { //arg == 'testValue' } mock('testValue'); //returns 'fake1' mock(); //throws unexpected arguments
Verify (fail)
import a from 'a'; var mock = a.mock(); mock.expect('testValue1').return('fake1'); mock.expect('testValue2').return('fake2'); mock('testValue1'); //returns 'fake1' mock.verify(); //throws mock has 1 pending functions
Verify (success)
import a from 'a'; var mock = a.mock(); mock.expect('testValue1').return('fake1'); mock.expect('testValue2').return('fake2'); mock('testValue1'); //returns 'fake1' mock('testValue2'); //returns 'fake2' mock.verify(); //returns true
returning void (compact syntax)
import a from 'a'; var mock = a.mock(); mock.expect('testValue1'); mock.expect('testValue2').repeat(2); mock('testValue1'); //returns undefined mock('testValue2'); //returns undefined mock('testValue2'); //returns undefined mock.verify(); //returns true
..is equivalent to ..
import a from 'a'; var mock = a.mock(); mock.expect('testValue1').return(); mock.expect('testValue2').return().repeat(2); mock('testValue1'); //returns undefined mock('testValue2'); //returns undefined mock('testValue2'); //returns undefined mock.verify(); //returns true
Reset mock
import a from 'a'; var original = function() { return 'realValue'; } var mock = a.mock(original); mock.expect().return('fake'); mock.reset(); mock(); //returns 'realValue'
Returning resolved promise
import a from 'a'; var mock = a.mock(); mock.expect('foo').resolve('fake'); mock('foo').then(function(returned){ //returned == 'fake' });
Returning rejected promise
import a from 'a'; var mock = a.mock(); mock.expect('foo').reject('fake'); mock('foo').then(null, function(returned){ //returned == 'fake' });
Strict mock - advanced scenario
import a from 'a'; var mock = a.mock(); mock.expect('testValue').ignore().whenCalled(onCalled).return('fake1'); function onCalled(arg,callback) { //arg == 'testValue' //callback == foo } function foo() { } mock('testValue', foo); //returns 'fake1' mock.verify() //returns true mock('testValue',foo); //throws unexpected arguments
Mocking an object
Partial object mock
import a from 'a'; function newCustomer(_name) { var c = {}; c.getName = function () { return _name; }; return c; } var customer = newCustomer('Alfonzo The Real'); var customerMock = a.mock(customer); customerMock.getName.expect().return('Johnny Fake'); customer.getName(); //returns Johnny Fake customer.getName(); //returns Alfonzo The Real customerMock.verify(); //returns true
Mocking require
expectRequire
var fakeDep = {}; var expectRequire = require('a').expectRequire; expectRequire('./realDep').return(fakeDep); require('./realDep'); //returns fakeDep require('./realDep'); //returns realDep (behaves like a partial mock)
requireMock (compact syntax)
var requireMock = require('a').requireMock; var fakeDep = requireMock('./realDep'); //returns a strict mock require('./realDep'); //returns fakeDep require('./realDep'); //returns realDep
..is equivalent to ..
var mock = require('a').mock(); var expectRequire = require('a').expectRequire; var fakeDep = mock; expectRequire('./realDep').return(fakeDep); require('./realDep'); //returns fakeDep require('./realDep'); //returns realDep
Reset mocks for require
var fakeDep = {}; var expectRequire = require('a').expectRequire; expectRequire('./realDep').return(fakeDep); expectRequire.reset(); require('./realDep'); //returns realDep
..is equivalent to ..
var requireMock = require('a').requireMock; var fakeDep = requireMock('./realDep'); //returns a strict mock requireMock.reset(); //is an alias for expectRequire.reset() require('./realDep'); //returns realDep
Mocking promises
Mocking resolve
import a from 'a'; var promise = a.promise(); //mocked promise promise.then(success,error); promise.resolve('success'); function success(arg) { console.log(arg);//success } function error(e) { //will not happen }
Mocking resolve (alternative syntax)
import a from 'a'; var promise = a.promise(); //mocked promise promise.then(success,error); promise('success'); function success(arg) { console.log(arg);//success } function error(e) { //will not happen }
Mocking reject
import a from 'a'; var promise = a.promise(); promise.then(success,error); promise.reject(new Error('error')); function success(arg) { //will not happen } function error(e) { console.log(e.stack);//will happen }
Mocking reject (alternative syntax)
import a from 'a'; var promise = a.promise(); promise.then(success,error); promise(null,new Error('error')); function success(arg) { //will not happen } function error(e) { console.log(e.stack);//will happen }
A test framework
From version 3.0.0 this is in a separate package: npmjs.com/package/a_test
A test framework is a simplistic, magic-free library providing unit-testing facilities with a compact, bdd-style syntax.
In contrast to other bdd-style test frameworks, it doesn't allow nesting suites in each other in order to test the SUT(subject under test) in different states. Instead, the framework relies on folder structure to describe the state. The SUT currently has that folder structure. Suite names are generated based on their filenames. As a result, there will be many small test files without nested test suites instead of a few big test files with nested test suites.
Test setup, the "Arrange-Act" part of suites, is separated from the "Assert" part. This way the same setup can be used across different suites. Test setups can be chained.
Examples below can be found here: https://github.com/alfateam/a_demo
Example
The test runner ( a ) will search for all files named 'when*.js' in the current and sub-directories.
Given the following file structure
- demo/
- counter.js
- counter_specs/
- new/
- increment.js
- when_incremented.js
- new.js
- when_new.js
- new/
counter.js
module.exports = function () { var counter = { value: 0, increment: function() { value++; } }; return counter; }
counter_specs/new.js
import a from 'a'; function act(c) { var createCounter = require('../counter'); c.sut = createCounter(); } module.exports = act;
counter_specs/when_new.js
import a from 'a'; var c = {}; //test context object var when = a.when; when('./new', c). //set up it('should be an object'). assertEqual('object', typeof c.sut) it('should have value equal to zero'). assertEqual(0, c.sut.value). it('should fail just for fun'). assertFail('error message');
counter_specs/new/increment.js
function act(c) { c.sut.increment(); } act.base = '../new'; module.exports = act;
counter_specs/new/when_incremented.js
import a from 'a'; var c = {}; var when = a.when; when('./increment', c). it('should have value equal to 1'). assertEqual(1, c.sut.value);
__In demo directory, run a __
user@localhost:~/a_demo $ a
» counter_specs » new
✓ should be an object
✓ should have value equal to zero
✘ should fail just for fun
» counter_specs » new » increment
✓ should have value equal to 1
========== Summary =============
counter_specs » new
✘ should fail just for fun
AssertionError: error message
at retval.assertFail (/home/user/a_demo/node_modules/a/when/it.js:14:11)
at Object.test (/home/user/a_demo/node_modules/a/when/test_invoker.js:5:3)
at Object.retval.assertFail (/home/user/a_demo/node_modules/a/when/it.js:13:5)
at Object.<anonymous> (/home/user/a_demo/counter_specs/when_new.js:11:3)
at Module._compile (module.js:449:26)
at Object.Module._extensions..js (module.js:467:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Module.require (module.js:362:17)
at require (module.js:378:17)
------------
suites: 2, passed: 3, failed: 1
Async test support
Modified act files should look like this:
import a from 'a'; function act(c) { ... return c.sut(c.arguments); //returns promise }
or
import a from 'a'; async function act(c) { ... await c.sut(c.arguments); }
Test file should look like this:
import a from 'a'; var when = a.when; var c = {}; when(c).then(it => { it('....').assertXXXX(); });