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.
Related
I am creating HTTP tests with frisby.js which works on top of jasmine.js.
I also have to create some mongoDB objects to test against.
The problem is when I want to clean up these DB objects. When one of the expects fail I want to intercept that and call my own cleanup function. This means that after each failed test, I won't be able to remove the test objects from the DB.
The afterEach function in jasmine does not work properly and jasmine does not have any support for afterAll or beforeAll yet.
That is why I have made the tests as they are today.
it("testing userform get with correct userID and expect correct return", function() {
var innerUserId = userID;
frisby.create('Should retrieve correct userform and return 200 when using a valid userID')
.get(url.urlify('/api/userform', {id: innerUserId}))
.expectStatus(200)
.afterJSON(function(userform){
// If any of these fail, the after function wont run.
// I want to intercept the error so that I can make sure that the cleanUp function is called
// afterEach does not work. I have tried with done()
var useridJSON = userform.UserId.valueOf();
var firstnameJSON = userform.firstname.valueOf();
var surnameJSON = userform.surname.valueOf();
expect(firstnameJSON).toMatch(testUser.firstName);
expect(surnameJSON).toMatch(testUser.surname);
expect(useridJSON).toMatch(innerUserId);
})
.after(function(){
cleanUp(innerUserId);
})
.toss();
});
I am wondering if there is a way to intercept the error for "expect" in frisby or jasmine so that I can make a call to my own cleanup function before exiting.
Full example here
The quickest solution to this problem is to wrap the error code in a try-catch.
This is because if a javascript error occurs, jasmine will NOT keep running assertions. This is different from an assertion error. If an assertion error occurs, jasmine and frisby will keep on testing all the other assertions and then do the "after"-function.
.afterJSON(function(userform){
try {
var useridJSON = userform.UserId.valueOf();
var firstnameJSON = userform.firstname.valueOf();
var surnameJSON = userform.surname.valueOf();
catch(e) {
cleanUp(innerUserId);
// Can do a throw(e.message); here aswell
}
expect(firstnameJSON).toMatch(testUser.firstName);
expect(surnameJSON).toMatch(testUser.surname);
expect(useridJSON).toMatch(innerUserId);
})
This is not the pretty way, but works.
I ended up adding the throw(e) and placed the expects in a finally scope. This way I got jasmine to present all the errors that occured in the test.
As for "before exiting", how about this:
process.on('uncaughtException', function(err) {
console.error(' Caught exception: ' + err);
});
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'
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
I encountered a strange error today.
When I invoked the below function in chrome, I got:
var t = function(i){console.log(i);console.log(eval("i"));};
t("123");
//the result in chrome
123
undefined
But the above code invoked in firefox, it came out just as I thought: the second log statement was the same as the first.
In my opinion,the eval statement will use the context of the anonymous function as its runtime context, which contains the parameters.
I didn't find any material involve eval context and parameter.
Can anyone tell me why ?
Actually,I used tempo.js to render html and came out a similar question as I listed above.
the source code is here:
_replaceVariables: function (renderer, _tempo, i, str) {
return str.replace(this.varRegex, function (match, variable, args) {
try {
...
if (variable === '.') {
val = eval('i');
} else if (utils.typeOf(i) === 'array') {
val = eval('i' + variable);
} else {
val = eval('i.' + variable);
}
.....
} catch (err) {
console.log(err);
}
return '';
});
},
When run in chrome,the eval statement got error like this:
TypeError: Cannot convert null to object
I can't figure out why this happened, so I tried the code at the beginning.
The Chrome console implementation of the console functions involves some asynchronous behavior that causes weird issues like what you've discovered.
That said, in your particular case my Chrome logs "123" twice. I find it generally to be a really good idea to augment debugging output with some unique identifying text:
var t = function(i){console.log("param is " + i);console.log("eval result is " + eval("i"));};
The Chrome console output collapses repeated lines and prefixes them with a little circled counter:
(source: gutfullofbeer.net)
That little "2" before "123" means it was logged twice.
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'});