I'm trying to write a plugin for Jasmine that allows you to return a promise from a spec and will pass or fail that spec depending on whether or not the promise is fulfilled or rejected.
Of course, I want to write tests to make sure that my plugin works correctly, and to be thorough, I need to make sure that tests fail when the promise is rejected... so how do I make a test pass when I need to make sure that a test "successfully fails"?
After a conversation with the developers who work on Jasmine, we've come up with this:
var FAILED = 'failed'
var PASSED = 'passed'
describe('My Test Suite', function () {
var env
beforeEach(function () {
// Create a secondary Jasmine environment to run your sub-specs in
env = new jasmine.Env()
})
it('should work synchronously', function () {
var spec
// use the methods on `env` rather than the global ones for sub-specs
// (describe, it, expect, beforeEach, etc)
env.describe('faux suite', function () {
spec = env.it('faux test', function (done) {
env.expect(true).toBe(true)
})
})
// this will fire off the specs in the secondary environment
env.execute()
// put your expectations here if the sub-spec is synchronous
// `spec.result` has the status information we need
expect(spec.result.status).toBe(FAILED)
})
// don't forget the `done` argument for asynchronous specs
it('should work asynchronously', function (done) {
var spec
// use the methods on `env` rather than the global ones.
env.describe('faux suite', function () {
// `it` returns a spec object that we can use later
spec = env.it('faux test', function (done) {
Promise.reject("FAIL").then(done)
})
})
// this allows us to run code after we know the spec has finished
env.addReporter({jasmineDone: function() {
// put your expectations in here if the sub-spec is asynchronous
// `spec.result` has the status information we need
expect(spec.result.status).toBe(FAILED)
// this is how Jasmine knows you've completed something asynchronous
// you need to add it as an argument to the main `it` call above
done()
}})
// this will fire off the specs in the secondary environment
env.execute()
})
})
Going off Joe's answer, I moved the fake test context into a single function. Since the code under test is making use of jasmine expectations, I load the inner Env into jasmine.currentEnv_ and call it explicitly with jasmine.currentEnv_.expect(). Note that currentEnv_ is an internal variable set by jasmine itself, so I can't guarantee that this won't be broken in a future jasmine version.
function internalTest(testFunc) {
var outerEnvironment = jasmine.currentEnv_;
var env = new jasmine.Env();
jasmine.currentEnv_ = env;
var spec;
env.describe("fake suite", function () {
spec = env.it("fake test", function () {
func();
});
});
env.execute();
jasmine.currentEnv_ = outerEnvironment;
return spec.result;
}
Then each test looks like
it("does something", function () {
//Arrange
//Act
var result = internalTest(function () {
//Perform action
});
//Assert
expect(result.status).toBe("failed"); //Or "success"
expect(result.failedExpectations.length).toBe(1);
expect(result.failedExpectations[0].message).toBe("My expected error message");
});
Related
I have a situation where I need to create a test that calls a function and checks its return value. It returns an object so I have to iterate through it and check 100 or so values for correctness. If one of them fails I want to know which one.
I cannot work out how to do this with vanilla Jest such that the test is self-contained and I get a meaningful error message on a failure.
For example, I can do this: (pseudocode to illustrate, not actual code)
describe('Test for function A', () => {
beforeAll('Create class instance', () => {
this.inst = new MyClass();
});
test('Call function with no parameters', () => {
const value = this.inst.run();
for (each key=value) {
expect(value).toBe(correct); // on failure, doesn't tell me the key, only the value
}
});
});
The problem with this is that if value is not correct then the error message is not very helpful, as it doesn't tell me which of the 100 values has the problem.
I can't change to test.each() because then I get an error saying I have nested test() calls which is not allowed.
If I use an inner test() and change the parent test() to describe() then the code becomes this:
describe('Test for function A', () => {
beforeAll('Create class instance', () => {
this.inst = new MyClass();
});
describe('Call function with no parameters', () => {
const value = this.inst.run();
for (each value) {
test(`Checking ${value.name}`, () => {
expect(value).toBe(correct);
});
}
});
});
This would give me a detailed error message except this.inst.run() is called during test set up, before this.inst has been set by beforeAll(), so it fails. (Jest runs all the describe() blocks first, then beforeAll(), then test(). This means I call this.inst.run() first in a describe() block before the beforeAll() block creates the class instance.)
Is there any way that this is possible to achieve? To have a test that requires an object created and shared amongst all the child tests, a test group that calls a function to get data for that group, then a bunch of tests within the group?
Yes, it is possible according to the order of execution of describe and test blocks:
describe("Test for function A", () => {
this.inst = new MyClass();
afterAll("Create class instance", () => { //--> use this instead of beforeAll
this.inst = new MyClass();
});
test("Should be defined", () => {
//--> at least one test inside describe
expect(inst).toBeTruthy();
});
describe("Call function with no parameters", () => {
const value = this.inst.run();
test("Should be defined", () => {
//--> at least one test inside describe
expect(value).toBeTruthy();
});
for (/*...each value */) {
test(`Checking ${value.name}`, () => {
expect(value).toBe(correct);
});
}
});
});
I came up with a workaround for this. It's a bit hacky but it seems to work. Essentially you use promises to wrap the value you're interested in, so one test will sit there await-ing the result from another test.
Obviously this will only work if the tests are run in parallel, or if the sequential ordering is such that the promise is resolved before it is awaited.
The only trick below is that the await is placed in a beforeAll() block, so that the value is available to all tests within that describe() section. This avoids the need to await in each individual test.
The benefit of this is that the test set up (creating the object) is within a test() so exceptions are captured, and the checks themselves (expect().toBe()) are in separate tests so that the test name can be set to something descriptive. Otherwise if your expect() calls are in a for loop, when one fails there's no way to figure out which array entry was at fault.
It's a lot of work just because you can't supply a description on the expect() call (unlike other testing frameworks), but if you're stuck with Jest then this does at least work. Hopefully one day they will add a per-expect description to avoid all this.
Here is some sample pseudocode:
describe('Test for function A', () => {
let resolveValue;
let promiseValue = new Promise(resolve => resolveValue = resolve);
describe('Create class instance', () => {
test('Run processing', () => {
this.inst = new MyClass();
// inst.run() is now called inside a test(), so any failures will be caught.
const value = this.inst.run();
resolveValue(value); // release 'await promiseValue' below
});
});
describe('Call function with no parameters', () => {
let value; // this is global within this describe() so all tests can see it.
beforeAll(async () => {
// Wait for the above test to run and populate 'value'.
value = await promiseValue;
});
for (each value) {
// Check each value inside test() to get a meaningful test name/error message.
test(`Checking ${value.name}`, () => {
// 'value' is always valid as test() only runs after beforeAll().
expect(value).toBe(correct);
});
}
});
});
I have a method that checks whether or not a global function is defined (it may or may not be available, depends on each client's request). If it is defined, it will call it with the appropriate data. If not, it will fail silently. That's the desired behavior.
What I want to do is test it. Is there a way to mock and/or spy on libFunction so that I can ensure it's being called once with the correct data (the function here is very much simplified, there's some data processing that happens along the way).
Here's the method in question:
function sendData(data) {
let exists;
try {
// eslint-disable-next-line no-undef
if (libFunction) exists = true;
} catch (e) {
exists = false;
}
if (exists) {
// eslint-disable-next-line no-undef
libFunction(data);
}
}
I've tried defining libFunction in my tests and then stubbing that, but that doesn't do what I want:
describe('sendEvent', function () {
function libFunction(data) {
console.log('hi', data);
}
it('should call libFunction once', function () {
var stub = sinon.stub(libFunction);
var data = "testing";
sendEvent(data);
expect(stub.called).to.be.true;
});
});
This test does not pass, however: AssertionError: expected undefined to be true
I've tried something similar with a spy:
describe('sendEvent', function () {
function libFunction(data) {
console.log('hi', data);
}
it('should call libFunction once', function () {
var spy = sinon.spy(libFunction);
var data = "testing";
sendEvent(data);
expect(spy.called).to.be.true;
});
});
This also fails: AssertionError: expected false to be true
Is there a way to do this?
FWIW, I came across this question while trying to solve an issue for stubbing a global method in Node. In my case, this worked (my example uses Sinon.sandbox, but "regular" Sinon.spy should work, as well):
const encodeSpy = sandbox.spy(global, "encodeURIComponent");
// later...
Sinon.assert.calledWith(encodeSpy, {expectedParamValue});
Note, related How to test if jQuery 3.0 beta is Promises/A+ compatible in browser?
For example, at promises-tests one of the tests is found at promises-tests/lib/tests/2.1.2.js
"use strict";
var assert = require("assert");
var testFulfilled = require("./helpers/testThreeCases").testFulfilled;
var adapter = global.adapter;
var deferred = adapter.deferred;
var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it
describe("2.1.2.1: When fulfilled, a promise: must not transition to any other state.", function () {
testFulfilled(dummy, function (promise, done) {
var onFulfilledCalled = false;
promise.then(function onFulfilled() {
onFulfilledCalled = true;
}, function onRejected() {
assert.strictEqual(onFulfilledCalled, false);
done();
});
setTimeout(done, 100);
});
specify("trying to fulfill then immediately reject", function (done) {
var d = deferred();
var onFulfilledCalled = false;
d.promise.then(function onFulfilled() {
onFulfilledCalled = true;
}, function onRejected() {
assert.strictEqual(onFulfilledCalled, false);
done();
});
d.resolve(dummy);
d.reject(dummy);
setTimeout(done, 100);
});
specify("trying to fulfill then reject, delayed", function (done) {
var d = deferred();
var onFulfilledCalled = false;
d.promise.then(function onFulfilled() {
onFulfilledCalled = true;
}, function onRejected() {
assert.strictEqual(onFulfilledCalled, false);
done();
});
setTimeout(function () {
d.resolve(dummy);
d.reject(dummy);
}, 50);
setTimeout(done, 100);
});
specify("trying to fulfill immediately then reject delayed", function (done) {
var d = deferred();
var onFulfilledCalled = false;
d.promise.then(function onFulfilled() {
onFulfilledCalled = true;
}, function onRejected() {
assert.strictEqual(onFulfilledCalled, false);
done();
});
d.resolve(dummy);
setTimeout(function () {
d.reject(dummy);
}, 50);
setTimeout(done, 100);
});
});
Requirement:
The ability to run the test in the browser without reliance on node.js , a server, or installing libraries?
Question:
How can this test be converted to a version using native methods available at browsers, for example Console methods; that is, substitution of console.assert() or other native method available at window for describe() and specify()?
I still think this is a terrible idea, but here's a (trivial) example:
var assert = {};
assert.equals = function(expected, value, msg) {
var message = msg || "Test ";
try {
expected == value;
console.log(message, "passed");
catch (e) {
console.log(message, "failed", e);
}
};
Now repeat for >, <, ===, ranges, exceptions when they're expected, specific exceptions (e.g. TypeError, SyntaxError), typeof, instanceof, other types (built-ins, constructed), problematic values like NaN and null, etc.
NOTE: console.assert
There are some problems with console.assert. First, its non-standard and may not behave identically across platforms. Second, there is, AFAIK, no good way to abstract it: you'd end up doing just as much work as the above solution using console.log unless you use eval and string arguments:
function assert(str, msg) {
try {
console.assert(eval(str), msg);
catch (e) {
console.log(msg, " failed:", e);
}
}
assert("3 === 4", "Three equals four"); // logs the assertion failure.
Needless to say, I do not recommend this, as manually constructing the strings is error-prone, eval (even though safe in this case) is a notorious perf killer, and not using eval means using a parser and bam we're right back to the library bit.
Seriously, as you go down this road you will think of more and more things you want to be in there (see my list above) and you will realize you are writing a library when you could have used one.
UPDATE
Per your question in the comments below, many testing libraries (e.g. mocha, jasmine) use a format like this:
wrapper('name of the thing being tested', function(done) {
innerWrapper('description of the test', function(done) {
assert(someAssertion);
});
innerWrapper('some other test for that function', function(done) {
assert(somethingElse);
someAsyncFunction().then(function(value) {
assert(somethingAboutValue);
done();
});
});
});
The 'wrapper', 'innerWrapper', and 'assert' are added by including the testing library in the code or running the command (for cli) i.e. 'mocha tests.js' instead of 'node tests.js'. The setup may or may not use the inner functions to specify subtests. 'Done' is an argument to the callback that can be used to signal the end of an async test.
QUnit is a little simpler in its API, but not too far off.
UPDATE 2
Those for the most part are names for the same things: functions that wrap a test condition to make sure that correct messages are logged, or exceptions caught, or async gets a chance to finish. The assert tests the actual condition to be evaluated. The adapter reference means wrapping the jQuery deferred constructor to match the API in the spec.
Wondering if anyone can help me - I'm trying to test my js using Jasmine (1.3) and I can't figure out the best way to test any method calls inside a .then or a .done method.
example code to explain:
Backbone.View.extend({
myMethod: function () {
this.something.done(function () {
this.doSomethingElse();
}.bind(this));
}
})
I'd like to write a test that check that this.doSomethingElse was called.
I was looking around at jasmine.async and a waitsFor/runs set up but I'm not sure how it fits into external code i.e. I'm not going to call done() inside my actual code to get my test working. Also if I mock out the done method on this.something then I'm not longer testing the actual implementation, right?
I'm just missing how things fit together. If anyone could point me in the right direction I'd really appreciate it!
Update: based on feedback below I've now tried the following
Hey, thanks for the answer - I think maybe I don't have the last part correct - have tried 2 different ways, both initial pass but then fail after a second or 2.
it('calls doSomethingElse on done',function () {
var mockDeferred = $.Deferred();
myView.something = mockDeferred;
spyOn(myView,'doSomethingElse');
mockDeferred.resolve();
waitsFor(function () {
expect(myView.doSomethingElse).toHaveBeenCalled();
});
});
And also:
it('calls doSomethingElse on done',function () {
var mockDeferred = $.Deferred(),
someTrigger = false;
myView.something = mockDeferred;
spyOn(myView,'doSomethingElse');
runs(function () {
mockDeferred.resolve();
someTrigger = true;
});
waitsFor(function () {
someTrigger = true;
});
runs(function () {
expect(myView.doSomethingElse).toHaveBeenCalled();
});
});
In both instances the test will pass originally but then timeout to a failure after a second or 2.
Am I missing something?
To test the example function you described, I would do the following within your test:
Create a new deferred object (I'll call it mockDeferred)
Pass mockDeferred into your code under test so that it is now this.something in your example
Spy on the doSomethingElse function
Call myMethod()
Call resolve() on mockDeferred
Assert that doSomethingElse was called
Edit based on OP's Update:
I don't see anywhere in either of your examples where you are calling myView.myMethod() within your test; make sure you do that. I whipped up an example that you can reference here.
As an aside, I'm surprised the second example you tried passes initially. Maybe because you have some code outside of a runs() block?
Related problem
Spying on a method inside .then and expecting .toHaveBeenCalled fails
Solution:
run test inside fakeAsync and run tick() before the expect
Service:
getFirebaseDoc() {
this.db.firestore.doc('some-doc').get()
.then(this.getFirebaseDocThen)
.catch(this.getFirebaseDocCatch);
}
Unit testing:
it('should call getFirebaseDocThen', fakeAsync(() => { // note `fakeAsync`
spyOn(service, 'getFirebaseDocThen');
spyOn(service.db.firestore, 'doc').and.returnValue({
get: (): any => {
return new Promise((resolve: any, reject: any): any => {
return resolve({ exists: true });
});
},
});
service.getFirebaseDoc();
tick(); // note `tick()`
expect(service.getFirebaseDocThen).toHaveBeenCalled();
}));
It appears that sinon.spy(object, method) is not wrapping my object#method as expected.
(I have an uneasy feeling that I'm seeing the same problem as described here and here, but I don't see why this should be. I've instantiated my object before calling sinon.spy(...), and AFAIK I'm not using any cached objects.)
Here's the complete test file:
var
AmbitParser = require('../lib/parsers/ambit-parser'),
expect = require('chai').expect,
sinon = require('sinon');
describe('AmbitParser', function() {
var ambit_parser = new AmbitParser();
describe('#extractLineItems()', function() {
it('calls extractLineItems once', function(done) {
var spy = sinon.spy(ambit_parser, 'extractLineItems');
ambit_parser.parseBills(function gotBills(err, bills) {
expect(ambit_parser.extractLineItems.callCount).to.equal(1); // => expected undefined to equal 1
expect(spy.callCount).to.equal(1); // => expected 0 to equal 1
done();
});
ambit_parser.extractLineItems.restore();
}); // calls extractLineItems once
}); // #extractLineItems
}); // AmbitParser
The call to expect(ambit_parser.extractLineItems.callCount).to.equal(1); results in 'expected undefined to equal 1' and if I change that to expect(spy.callCount).to.equal(1);, I get 'expected 0 to equal 1'.
Together, this makes me think that the call to sinon.spy(...) is not wrapping the ambit_parser.extractLineItems method as expected, but I can't see why this is the case.
The problem is the placement of the call to restore(): it should not be in the body of the test function. Instead, put it in an after() block.
What's happening is that the restore() method is getting called immediately after the test is started, so by the time the callback is executed, the spied upon method has been restored, so sinon will report that the method has never been called.
The following modifications to the original code will work as expected:
describe('AmbitParser', function() {
var ambit_parser = new AmbitParser();
describe('#extractLineItems()', function() {
before(function() {
sinon.spy(ambit_parser, 'extractLineItems');
});
after(function() {
ambit_parser.extractLineItems.restore();
});
it('calls extractLineItems once', function(done) {
ambit_parser.parseBills(function gotBills(err, bills) {
expect(ambit_parser.extractLineItems.callCount).to.equal(1);
done();
});
}); // calls extractLineItems once
}); // #extractLineItems
}); // AmbitParser
Moral of the story: make sure you call reset() only after any callbacks have completed.