When using Mocha and chai, when to wrap expectation with a function? - javascript

For code that throws exceptions, I need to wrap the expectation in an anonymous function. Otherwise the exception is thrown before it can be caught by Mocha.
See this StackOverflow answer.
But wrapping seems to have side effects.
The following code behaves differently when wrapped. it seems to be a Chai problem.
chai = require 'chai'
expect = chai.expect
describe 'Weird', ->
obj =
name: 'kalle'
it 'not wrapped', () ->
expect(obj).to.have.property 'name', 'kalle'
it 'wrapped', () ->
expect(->obj).to.have.property 'name', 'kalle'
My handcrafted Javascript version:
var expect = require('chai').expect;
describe('Weird', function() {
obj = {name: 'kalle'};
it('not wrapped', function() {
expect(obj).to.have.property('name', 'kalle');
});
it('wrapped', function() {
expect(function() {return obj}).to.have.property('name', 'kalle');
});
});
The failure looks like this:
AssertionError: expected [Function] to have a property 'name' of 'kalle', but got ''
Weird
✓ not wrapped
1) wrapped
1 passing (10ms)
1 failing
1) Weird wrapped:
+ expected - actual
+kalle
Why does the wrapping yield different results?
Thanks in advance!

In both cases you are passing the function as a parameter, not the result of the function. I expect that mocha actually calls the function when you call to.throw as is done in the linked SO answer. In order to resolve the function in this situation, you'd want to actually call the function in your expectation:
expect(->obj()).to.have.property 'name', 'kalle'

Related

Unexpected Sinon.js behaviour

I'm trying to stub the Stripe module for unit testing using Mocha and Sinon.js.
I require Stripe like this :
const stripe = require('stripe');
const stubbedStripeClient = stripe.Stripe('test');
At the root of my tests (inside my top-level describe()) I have this :
before('stub root', () => {
sinon.stub(stripe, 'Stripe').returns(stubbedStripeClient);
});
Then, in the describe() block where I actually would call a Stripe method, I have this before() hook :
let stub;
before('stub', () => {
console.log(typeof stubbedStripeClient.customers.create);
stub = sinon.stub(stubbedStripeClient.customers, 'create', ({id: 'a-stripe-customer-id'}));
});
This is where I don't understand what happens. The first line in the hook (console.log) outputs:
function
The second line throws this exception :
TypeError: Attempted to wrap undefined property create as function
How is this possible? How can it be a function on one line and be undefined on the very next line?
I looked at the Sinon.js source, and this check is performed here. If I then look at their isFunction function, it performs the same check I have in my console.log. I'm puzzled.
That is an unfortunate and misleading error message.
The 3rd argument to the stub call is not a function but an object. From the docs it needs to be a function.
To resolve, change:
({id: 'a-stripe-customer-id'})
to something like:
() => { return {id: 'a-stripe-customer-id'}; }
...if you want to return that object, or perhaps you meant the object as a parameter:
({id: 'a-stripe-customer-id'}) => {}

Expect item in array

One of my test expects an error message text to be one of multiple values. Since getText() returns a promise I cannot use toContain() jasmine matcher. The following would not work since protractor (jasminewd under-the-hood) would not resolve a promise in the second part of the matcher, toContain() in this case:
expect(["Unknown Error", "Connection Error"]).toContain(page.errorMessage.getText());
Question: Is there a way to check if an element is in an array with jasmine+protractor where an element is a promise?
In other words, I'm looking for inverse of toContain() so that the expect() would implicitly resolve the promise passed in.
As a workaround, I can explicitly resolve the promise with then():
page.errorMessage.getText().then(function (text) {
expect(["Unknown Error", "Connection Error"]).toContain(text);
});
I'm not sure if this is the best option. I would also be okay with a solution based on third-parties like jasmine-matchers.
As an example, this kind of assertion exists in Python:
self.assertIn(1, [1, 2, 3, 4])
Looks like you need a custom matcher. Depending on the version of Jasmine you are using:
With Jasmine 1:
this.addMatchers({
toBeIn: function(expected) {
var possibilities = Array.isArray(expected) ? expected : [expected];
return possibilities.indexOf(this.actual) > -1;
}
});
With Jasmine 2:
this.addMatchers({
toBeIn: function(util, customEqualityTesters) {
return {
compare: function(actual, expected) {
var possibilities = Array.isArray(expected) ? expected : [expected];
var passed = possibilities.indexOf(actual) > -1;
return {
pass: passed,
message: 'Expected [' + possibilities.join(', ') + ']' + (passed ? ' not' : '') + ' to contain ' + actual
};
}
};
}
});
You'll have to execute this in the beforeEach section on each of your describe blocks it's going to be used in.
Your expect would look like:
expect(page.errorMessage.getText()).toBeIn(["Unknown Error", "Connection Error"]);
The alternative solution is to use .toMatch() matcher with Regular Expressions and specifically a special character | (called "or"), which allows to match only one entry to succeed:
expect(page.errorMessage.getText()).toMatch(/Unknown Error|Connection Error/);
To me, the work-around that you identified is the best solution. However, we should not forget that this is an asynchronous execution and you might want to consider Jasmine's asynchronous support.
Then, your test will look like the following one:
it('should check item is in array', function(done){ //Note the argument for callback
// do your stuff/prerequisites for the spec here
page.errorMessage.getText().then(function (text) {
expect(["Unknown Error", "Connection Error"]).toContain(text);
done(); // Spec is done!
});
});
Note: If you don't pass this done argument to the spec callback, it is going to run to completion without failures, but no assertions are going to be reported in the execution results for that spec (in other words, that spec will have 0 assertions) and it might lead to confusions.

Don't sinon.js spys catch errors?

So, I'm using mocha with chai to do my front-end testing, but I'm starting to incorporate sinon and really liking it. Except that testing throwing errors isn't working quite how the sinon docs seem to indicate.
Basically, I've got this method:
create: function(bitString, collectionType) {
var collection;
switch(collectionType) {
case 'minutesOfHour':
collection = this.createMinutesOfHour(bitString);
break;
case 'hoursOfDay':
collection = this.createHoursOfDay(bitString);
break;
case 'daysOfWeek':
collection = this.createDaysOfWeek(bitString);
break;
case 'daysOfMonth':
collection = this.createDaysOfMonth(bitString);
break;
case 'monthsOfYear':
collection = this.createMonthsOfYear(bitString);
break;
default:
throw new Error('unsupported collection type ' + collectionType);
}
return collection;
},
and I'm testing it with this expectation:
it('throws error if missing second arguement', function() {
sinon.spy(factory, 'create');
factory.create();
expect(factory.create).to.have.thrown();
factory.create.restore();
});
however, the error, which I'm try to test for, also seems to halt the execution of the test
I'd thought sinon.spy would include some try / catch logic internally, spy.throw doesn't seem as useful without it.
http://sinonjs.org/docs/#spies
Am I doing something wrong??
I think one thing you could try is asserting against a spy object instead of the method, assign it to a variable. Not really knowing how sinon deals with all this exception magic...I think it might just work as you had expected.
it('throws error if missing second argument', function() {
var spy = sinon.spy(factory, 'create');
factory.create();
expect(spy).to.have.thrown();
factory.create.restore();
});
If that still doesn't work I think you could also do this test with standard chai if need be, leaving sinon out of the equation and actually gaining the check that the error has the correct message.
it('throws error if missing second argument', function() {
expect(function() {
factory.create();
}).to.throw(/unsupported collection type/);
});
Or more concisely:
it('throws error if missing second argument', function() {
expect(factory.create).to.throw(/unsupported collection type/);
});
In your expectation, you mixed chai and sinon syntax. Try
expect(factory.create.threw()).to.be.ok();
Sometimes you'd like to check that errors were thrown on a function that you're not directly testing, i.e. the error method of an ajax call.
This approach worked swell for me:
errorStub = sinon.stub(jQuery, "ajax").yieldsTo("error");
try {
triggerError(); // triggers ajax call which yields to an error
}
expect(errorStub.threw()).to.be.true

How to pass a custom error message to a jasmine matcher?

In all the testing frameworks I have used, there is an optional parameter to specify you own custom error message.
This can be very useful, and I can't find a way to do this out of the box with jasmine.
I've had 3 other developers ask me about this exact functionality, and when it comes to jasmine I don't know what to tell them.
Is it possible to specify your own custom error message on each assertion ?
Update 2022; Use .withContext(...) method instead of below (as optional parameter is deprecated).
Jasmine already supports optional parameter in all matchers (toBe, toContain, and others), so you can use:
expect(true).toBe(false, 'True should be false').
Then in output it will look like this:
Message:
Expected true to be false, 'True should be false'.
Link to commit (this is not described in documentation):
https://github.com/ronanamsterdam/DefinitelyTyped/commit/ff104ed7cc13a3eb2e89f46242c4dbdbbe66665e
If you take a look at the jasmine source code you will see that there is no way to set the message from outside a matcher. For example the toBeNaN matcher.
/**
* Matcher that compares the actual to NaN.
*/
jasmine.Matchers.prototype.toBeNaN = function() {
this.message = function() {
return [ "Expected " + jasmine.pp(this.actual) + " to be NaN." ];
};
return (this.actual !== this.actual);
};
As you can see the messages is hard coded into the matcher and will be set when you call the matcher. The only way I can think of to have your own messages is to write your matcher like described here
This issue is tracking interest in implementing custom error messages using a .because() mechanism.
In the meantime, avrelian has created a nice library which implements custom error messages using a since() mechanism – jasmine-custom-message.
Yes, it can be done.
You may define a custom matcher in global scope, overriding the error message in jasmine as below:
beforeEach(function () {
jasmine.addMatchers({
toReport: function () {
return {
compare: function (actual, expected, msg) {
var result = {pass: actual == expected};
result.message = msg;
return result;
}
}
}
});
});
Chain-call withContext() right after expect(). Example:
expect(myValue)
.withContext("This message will be printed when the expectation doesn't match")
.toEqual({foo: 'bar'});

Spying on console.error() with Jasmine

I'm actually new to JavaScript as well as Jasmine. So it might be something really obvious that fixes my problem but I can't see it.
I want to check if (an already existing) JavaScript application calls console.error() while loading. I don't really see a way how to realise this with Jasmine. I've included the JavaScript file as well as the spec file in the SpecRunner.html.
But I take it that I somehow need to "instantiate" the application in order to test if it throws any errors on the console, right?
Or should I include the SpecRunner.html code only for this purpose into the HTML code of the app?
You can spy on console.error like this:
beforeEach(function(){
spyOn(console, 'error');
})
it('should print error to console', function(){
yourApp.start();
expect(console.error).toHaveBeenCalled();
})
You can override the standard console.error function like this:
//call the error function before it is overriden
console.error( 'foo' );
//override the error function (the immediate call function pattern is used for data hiding)
console.error = (function () {
//save a reference to the original error function.
var originalConsole = console.error;
//this is the function that will be used instead of the error function
function myError () {
alert( 'Error is called. ' );
//the arguments array contains the arguments that was used when console.error() was called
originalConsole.apply( this, arguments );
}
//return the function which will be assigned to console.error
return myError;
})();
//now the alert will be shown in addition to the normal functionality of the error function
console.error( 'bar' );
This solution works with Jasmin or anything else. Just put the code above before the other codes and any call after this to console.error() will call the overridden function.
use toThow and toThrowError http://jasmine.github.io/edge/introduction#section-Spies:_and.throwError

Categories

Resources