Cypress test not failing from exception in mocked object - cypress

My test contains something like this:
it.only('does something', function() {
cy.window().then(function(win) {
win.GlobalObject = {
someMethod: function(data) {
return expect(data).to.deep.eq({
company: 'Pied Piperz',
country: 'United States'
});
}
};
});
cy.get('[data-cy=submit]').click();
});
When my test runs, part of the logic invokes window.GlobalObject.someMethod({}) which should fail the test since I didn't pass the expected object into someMethod(). Instead, I see a failing assertion in the log:
But the overall test is marked as succeeding:
How can I get get the failing asserting inside my mocked GlobalObject to fail the whole test?

The problem is that your test is failing AFTER it has already passed.
Once Cypress finishes cy.get('[data-cy=submit]').click(), it's done executing all the commands, so it considers your test to have completed.
Then, some milliseconds later, you have a failing expect() - but Cypress has already passed the test, so there is nothing meaningful to be done.
But all is not lost! You can use the optional done parameter to manually tell Cypress when the test has finished, like so:
it.only('does something', function(done) { // <--- note `done` here
cy.window().then(function(win) {
win.GlobalObject = {
someMethod: function(data) {
expect(data).to.deep.eq({
company: 'Pied Piperz',
country: 'United States'
});
done(); // <--- this call tells Cypress the test is over
}
};
});
cy.get('[data-cy=submit]').click();
});

Related

How to test a callback of a mocked function with jasmine?

A method of a third party service that I am using has a callback as the second argument. This callback is executed in real life when the response is received from the server.
I want to mock the third party method for unit testing, and supply different response arguments to the callback to ensure that its logic executes correctly. For example to check that the promise is rejected when the status is NOT 'success', or that on success just the saved record is returned and not the whole response.
I am using jasmine for testing.
function save() {
var deferred = $q.defer();
thirdPartyService.doSave(record, function callback(response) {
// How to test the code in here when doSave is mocked?
if(response.status === 'success') {
deferred.resolve(response.savedRecord);
} else {
deferred.reject(response);
}
});
return deferred.promise;
}
Example of a test I'd like to run:
// Setup
const successResponse = {
status: 'success',
savedRecord: { Id: 'test-id' }
};
// Somehow config the mocked thirdParty.doSave() to use successResponse for the callback.
// Test
myService.save()
.then(function(response) {
expect(response.Id).toBe('test-id');
});;
You could mock thirdParty.doSave using spyOn.and.callFake.
const successResponse = {
status: 'success',
savedRecord: { Id: 'test-id' }
};
spyOn(thirdParty, 'doSave').and.callFake((record, callback) => callback(successResponse));

cy.wrap() - difference between passing a string or object

After working with Cypress for a short time i've noticed a strange behavior with cy.wrap(). While wrapping string inside before hook said string is available across all further tests but object would only be available in first test, while yielding undefined in rest of them.
I've tried wrapping tests with context(), which worked, but unnecessarily clogged the cypress test tree UI.
Without context:
describe('Wrap test', function () {
before(function () {
console.log('Before');
cy.wrap('string').as('string');
cy.wrap({ object: true }).as('object');
});
it('Test 1', function () {
console.log('Test 1');
console.log(this.string); // 'string'
console.log(this.object); // { object: true }
});
it('Test 2', function () {
console.log('Test 2');
console.log(this.string); // 'string'
console.log(this.object); // undefined
});
});
With context:
describe('Wrap test', function () {
before(function () {
console.log('Before');
cy.wrap('string').as('string');
cy.wrap({ object: true }).as('object');
});
context('Context', function () {
it('Test 1', function () {
console.log('Test 1');
console.log(this.string); // 'string'
console.log(this.object); // { object: true }
});
it('Test 2', function () {
console.log('Test 2');
console.log(this.string); // 'string'
console.log(this.object); // { object: true }
});
})
});
I'd like to mock an object in before hook and afterwards use it in few tests. I thought about converting said object into JSON format, as it would be available for all the tests. Looking forward to hearing opinions from more experienced people about presented scenario.
Turns out that clearing context between tests is actually Cypress' intentional behavior, as described in docs here.
When you wrap your individual test cases in another test suite (describe/context), it won't clear the context (as you're seeing) for that suite if the context was created a level above --- this is intentional, too.
The fact that it only clears primitives, and not objects, is a bug, though, and I've created a bug report for this: mocha context cleared between tests only for primitives.

Expected and Actual Values in Failed Protractor Test Cases

I have recently installed the jasmine-spec-reporter package in order to produce more verbose and helpful logging during execution of test suites.
I want to be able to log expected and actual values for failed test cases and was wondering if I needed to explicitly have a statement in the form of expect(someCondition).toEqual(true); in order for me to see these values.
For example, I have a function like the following:
it('should log in', function(done) {
var customerNameElement;
customerNameElement = element.all(by.xpath('some_xpath')).first();
core.infoMessage(browser, JSON.stringify(params, undefined, 4));
login.login(browser, undefined, undefined, getWaitTime())
.then(function() {
return browser.wait(protractor.ExpectedConditions.and(
function() { return core.isUnobscured(browser, customerNameElement);
}, protractor.ExpectedConditions.elementToBeClickable(customerNameElement)), getWaitTime());
})
.catch(fail)
.then(done);
});
I can still log the failure to the console but not in the form that I'd like. Does jasmine-spec-reporter handle this or do I have to add the statement from above in each test case?
Also, does the fail keyword in the .catch() have any properties I can use to my advantage? It comes from:
// Type definitions for Jasmine 2.5.2 // Project: http://jasmine.github.io/
Thanks
you can try adding the below in protractor_config file:
let SpecReporter = require('jasmine-spec-reporter').SpecReporter;
exports.config = {
// your config here ...
onPrepare: function () {
jasmine.getEnv().addReporter(new SpecReporter({
spec: {
displayStacktrace: true
}
}));
}
}
Also, add the print function in the jasmineNodeOptssection
jasmineNodeOpts: {
...
print: function() {}
}

How to exit Protractor test from Spec on specific condition?

I have a suite which includes multiple specs. Each spec uses code on some libraries that return a rejected promise upon failure.
I can easily catch those rejected promises inside my spec. What I'm wondering about is that if I can make Protractor exit the whole suite inside that catch function, because the next specs inside the same suite are dependent on the success of the previous specs.
Pretend I have a suite called testEverything which has these specs openApp,signIn,checkUser,logout. If openApp fails, all next specs will fail due to dependency.
Consider this code for openApp:
var myLib = require('./myLib.js');
describe('App', function() {
it('should get opened', function(done) {
myLib.openApp()
.then(function() {
console.log('Successfully opened app');
})
.catch(function(error) {
console.log('Failed opening app');
if ( error.critical ) {
// Prevent next specs from running or simply quit test
}
})
.finally(function() {
done();
});
});
});
How would I exit the whole test?
There is a module for npm called protractor-fail-fast. Install the module npm install protractor-fail-fast. Here's an example from their site where you would place this code into your conf file:
var failFast = require('protractor-fail-fast');
exports.config = {
plugins: [{
package: 'protractor-fail-fast'
}],
onPrepare: function() {
jasmine.getEnv().addReporter(failFast.init());
},
afterLaunch: function() {
failFast.clean(); // Cleans up the "fail file" (see below)
}
}
Their url is here.
I have managed to come up with a workaround. Now the actual code that I used is way more complex, but the idea is the same.
I added a global variable inside protractor's config file called bail. Consider the following code at the top of config file:
(function () {
global.bail = false;
})();
exports.config: { ...
The above code uses an IIFE (Immediately Invoked Function Expression) which defines bail variable on protractor's global object (which would be available throughout the whole test).
I also have written asynchronous wrappers for the Jasmine matchers I need, which would execute an expect expression followed by a comparison, and return a promise (using Q module). Example:
var q = require('q');
function check(actual) {
return {
sameAs: function(expected) {
var deferred = q.defer();
var expectation = {};
expect(actual).toBe(expected);
expectation.result = (actual === expected);
if ( expectation.result ) {
deferred.resolve(expectation);
}
else {
deferred.reject(expectation);
}
return deferred.promise;
}
};
}
module.exports = check;
Then at the end of each spec, I set the bail value based on the spec's progress, which would be determined by the promise of those asynchronous matchers. Consider the following as first spec:
var check = require('myAsyncWrappers'); // Whatever the path is
describe('Test', function() {
it('should bail on next spec if expectation fails', function(done) {
var myValue = 123;
check(myValue).sameAs('123')
.then(function(expectation) {
console.log('Expectation was met'); // Won't happen
})
.catch(function(expectation) {
console.log('Expectation was not met'); // Will happen
bail = true; // The global variable
})
.finally(function() {
done();
});
});
});
Finally, on the beginning of next specs, I check for bail and return if necessary:
describe('Test', function() {
it('should be skipped due to bail being true', function(done) {
if ( bail ) {
console.log('Skipping spec due to previous failure');
done();
return;
}
// The rest of spec
});
});
Now I want to mention that there's one module out there called protractor-fail-fast which bails on the whole test whenever an expectation fails.
But in my case, I needed to set that bail global variable depending on which type of expectation has been failed. I ended up writing a library (really small) that distinguishes failures as critical and non-critical and then using that, specs would be stopped only if a critical failure has occurred.

Using jasmine to test amplifyjs request call backs

I'm using amplifyjs for AJAX requests. That's working fine. I'm using jasmine to test the code. I'm wondering what the best method is to test the success and error call backs. The current unit test I've written doesn't work because the call back is executed after the jasmine expect. Here's my code under test:
function logout() {
ns.busy.show();
amplify.request({
resourceId: 'logout',
success: _logoutSuccess
});
};
function _logoutSuccess(response) {
ns.busy.hide();
};
Here's the unit test, where I want to validate that a function is called when the request is returned:
it('should hide busy when successful', function () {
// arrange
ns.busy = { show: function () { }, hide: function () { } };
spyOn(ns.busy, 'hide');
amplify.request.define('logout', function (settings) {
settings.success({});
});
// act
ns.accountLogoutViewModel.logout();
// assert
expect(ns.busy.hide).toHaveBeenCalled();
});
Note: ns is just a variable holding the application namespace. If I place break points on the expect and on the ns.busy.hide() in the _logoutSuccess function, jasmine hits the expect and then hits the _logoutSuccess, hence the spyOn fails.
As I say, the code is working, I just want to know how to write a test for it. I've looked into the jasmine done() function, but I'm not sure how to use it in this circumstance (or even if it is a solution).
Thanks
Isn't it always the way, when I finally get round to posting a question, I then immediately find the answer. Here's the test that passes, using the jasmine done() function:
it('should hide busy when successful', function (done) {
// arrange
ns.busy = { show: function () { }, hide: function () { } };
spyOn(ns.busy, 'hide');
amplify.request.define('logout', function (settings) {
settings.success({});
done();
});
// act
ns.accountLogoutViewModel.logout();
// assert
expect(ns.busy.hide).toHaveBeenCalled();
});

Resources