promise-puppeteer
v1.1.0
Published
Easy to use JS promise test double/fake/mock and execution analysis library
Downloads
2
Maintainers
Readme
Promise Puppeteer
Table Of Contents
- Section 1: Introduction
- Section 2: Setup
- Section 3: Test Examples
- Section 4: PromiseFake and Thenable APIs
- Section 5: Version History
Introduction
Promise Puppeteer is a drop-in test solution for handling ES-Next and Promise/A+ promises in your code under test. The goal of Promise Puppeteer is to make it as smooth and easy as possible to test code consuming promises without having to do a bunch of test gymnastics.
Why use Promise Puppeteer:
- Easy to use -- API is exposed to developer to allow for full control over promise execution
- Predictable -- Executes promise code as it behaves in the wild, making it easier to verify everything works as expected
- Deterministic execution -- Your test is separated from the world: resolve or reject from within tests
- Clear communication -- Built to fail when code acts unpredictably: Resolve and reject throw when called twice; Optionally throws an error if no catch or onFailure behavior is registered (default: on)
- Easy execution analysis -- Optional step-by-step execution of resolve behavior for easier analysis
Compatibility:
- ES-Next Promise Standard:
- Promise
- all
- race
- new Promise()
- Thenable
- then
- catch
- finally
- Promise
- Promise/A+
- then supports both onSuccess and onFailure optionally
Promise Puppeteer works both in Node and client-side code, so it integrates seamlessly into all of your test scenarios. With its clear, simple API, it is easy to either execute through the entire code stack or perform step-by-step evaluation of promise resolution.
Setup
Setup is as simple as performing an NPM installation and including the library in your test environment:
npm install promise-puppeteer --save-dev
The library exists at the following path:
project-root/node_modules/promise-puppeteer/index.js
That's it!
Test Examples
A common test example
A common test setup scenario would look like the following (using the Mocha test framework):
const sinon = require('sinon');
const promiseDoubleFactory = require('promise-puppeteer');
const moduleUnderTestFactory = require('./moduleUnderTestFactory');
describe('Module Under Test', function () {
let thenableFake;
let moduleUnderTest;
beforeEach(function () {
thenableFake = promiseDoubleFactory.getThenableFake();
const MyService = require('./MyService');
sinon.stub(MyService, 'doAsyncThing').callsFake(() => thenableFake);
moduleUnderTest = moduleUnderTestFactory(MyService);
});
it('consumes a promise', function() {
// Call method under test
moduleUnderTest.doSomethingAsync();
// Initiate promise resolution, firing all thens, catches and finallys
thenableFake.resolve({ foo: 'bar' });
// perform test assertion around result from doSomethingAsync
});
});
A running thenable fake
An example of consuming a Promise Puppeteer thenable straight from the actual test suite is as follows:
const thenableFake = getThenableFake();
const thenStub = sinon.stub();
thenableFake
.then(value => value + 1)
.then(value => value * 2)
.then(thenStub)
.catch(() => null)
.resolve(5);
assert.equal(thenStub.args[0][0], 12);
It's worth noting thenableFake
adheres to the ES-Next promise standard, with the addition of resolve,
which will execute the promise as if it were resolved from an async executor (action).
Step-wise then resolution
You can step through a promise resolution with the resolveNext
function. The following execution allows the user to analyze what happens as each then executes and exits.
const thenableFake = getThenableFake();
const thenStub = sinon.stub();
thenableFake
.then(value => value + 1)
.then(value => value * 2)
.then(thenStub)
.catch(() => null);
const result1 = thenableFake.resolveNext(5); // runs the first 'then'
const result2 = thenableFake.resolveNext(result1); // runs the second 'then'
const result3 = thenableFake.resolveNext(result2); // runs the third 'then'
assert.equal(thenStub.args[0][0], result3);
assert.equal(result3, 12);
Rejecting a thenable fake
Rejecting a thenable fake will work exactly as expected, executing no then onSuccess functions, jumping straight to the catch.
const thenableFake = getThenableFake();
const thenStub = sinon.stub();
const catchStub = sinon.stub();
thenableFake
.then(value => value + 1)
.then(value => value * 2)
.then(thenStub)
.catch(() => null)
.reject(new Error('Because.'));
assert.equal(thenStub.callCount, 0);
assert.equal(thenStub.args[0][0].message, 'Because.');
PromiseFake and Thenable APIs
PromiseFake API
// Getting a new instantiable PromiseFake -- required for test state safety
const PromiseFake = promiseDoubleFactory.getPromiseFake();
// Promise.all
const thenableFake = PromiseFake.all([ /* these promises are not processed. */ ]);
// Promise.race
const thenableFake = PromiseFake.race([ /* these promises are not processed. */ ]);
// Instantiation
const thenableProxyFake = new PromiseFake(function(resolve, reject){
// Code goes here
// Resolving and rejecting DOES NOT initiate actual promise resolution
});
// Available methods on thenableProxyFake:
thenableProxyFake.then();
thenableProxyFake.catch();
thenableProxyFake.finally();
thenableProxyFake.disableThrowOnNoCatch();
// Access to internal values:
const resolveArguments = thenableProxyFake.resolve.args;
const rejectArguments = thenableProxyFake.reject.args;
Thenable Fake API
// Getting a new thenable fake object
const thenableFake = promiseDoubleFactory.getThenableFake();
// Chaining thens, catches and finallys:
thenableFake
.then(onSuccess1, onFailure1)
.then(onSuccess2)
.catch(onFailure2)
.catch(onFailure3)
.finally(onComplete1)
.finally(onComplete2);
// Turning off errors when catch is missing on a thenable fake
thenableFake.disableThrowOnNoCatch();
// Resolve and execute onSuccess and onComplete functions completely:
thenableFake.resolve(arg1, arg2, ...);
// Reject and execute onFailure and onComplete functions completely:
thenableFake.reject(new Error('Something bad happened'));
Special Case and Analysis Step-wise Resolution
// Verify resolveNext can be called safely:
thenableFake.canResolve();
// Resolve onSuccess functions incrementally, calling onComplete functions upon last onSuccess execution completion:
const outcome = thenableFake.resolveNext(arg1, arg2, ...); // Returns outcome from internal execution
Version History
v1.0.0
Initial release:
- PromiseFake
- all
- race
- ThenableFake
then
catch
finally
resolve
resolveNext
reject