Erroneous calls to stub in Cypress tests - cypress

I'm trying to assert that a method is called, so I've stubbed it:
import * as dataActions from '../../../src/redux/data.actions';
it.only('sends start request', () => {
cy.visit('/')
const stub = cy.stub(dataActions, 'handleMessage', () => console.trace('HELLO FROM FAKE')).as('messageStub')
expect(stub).not.to.be.called;
cy.contains(/start/i).click() // this will call handleMessage
expect(stub).to.be.called;
});
I have confirmed that the stub is definitely preventing the actual handleMessage from being called. However, the test fails on the final assertion. And it does so immediately, without any of Cypress's famous timeout.
But although it fails, the stub is somehow called four times. I can see "HELLO FROM FAKE" in the console, and the cypress log shows four invocations of the stub:
And the behavior is very strange: If I comment out the final assertion, the method is not stubbed (the actual implementation is called) and the fake is never called.
What is going on here?
UPDATE: A trip to the cypress docs implies that you're supposed to assert on the actual method, not the returned stub. However, the following gets the exact same result:
it.only('sends start request', () => {
cy.visit('/')
cy.stub(dataActions, 'handleMessage', () => console.log('HELLO FROM FAKE')).as('messageStub')
cy.contains(/start/i).click()
expect(dataActions.handleMessage).to.have.been.called;
});

Like it was said in the comments, you must wait for the click. So the correct code should be:
it.only('sends start request', () => {
cy.visit('/')
cy.stub(dataActions, 'handleMessage', () => console.log('HELLO FROM FAKE')).as('messageStub')
cy.contains(/start/i).click().then(() => {
expect(dataActions.handleMessage).to.have.been.called;
});
});

Related

How to listen global events with Cypress?

We have an application that polls the server periodically until a task is completed. We fire a global event so that Cypress can catch and find out if the task is finished but we had trouble using document.addEventListener on Cypress. Here's what we're doing:
document.addEventListener('queryEnd', () => {
cy.get('.chart').should('be.visible')
cy.get('.table').should('be.visible')
})
However; when we use it in a spec, it doesn't work expected and we're not able to catch it. Also, Cypress doesn't wait for the test and runs afterEach without waiting for the callback to run.
The reason why your code isn't working like you expect is because in Cypress, your tests run in a separate frame than your application under test (AUT). The event you're waiting for will never fire inside of Cypress's document.
To get the document of the AUT, use cy.document() like this:
cy.document()
.then($document => {
// now $document is a reference to the AUT Document
$document.addEventListener(...)
})
To make Cypress wait for your event before continuing, you can just wrap it in a Cypress.Promise. The Cypress docs have an example about waiting for a Promise to complete. For your queryEnd event, it would look something like this:
cy.document() // get a handle for the document
.then($document => {
return new Cypress.Promise(resolve => { // Cypress will wait for this Promise to resolve
const onQueryEnd = () => {
$document.removeEventListener('queryEnd', onQueryEnd) // cleanup
resolve() // resolve and allow Cypress to continue
}
$document.addEventListener('queryEnd', onQueryEnd)
})
})
.then(() => {
cy.get('.chart').should('be.visible')
cy.get('.table').should('be.visible')
})

Mocha timing out when test in Promise callback fails

If I have the following module:
module.exports = kontinue => {
Promise.resolve({error:null})
.then(o => {
console.log('promise resolved');
// say something goes wrong here
if(true)
kontinue({error:'promise resolved but something else went wrong'});
else kontinue(o);
})
.catch(error => {
console.log('caught error');
kontinue({error:'promise rejected, or resolved but then continuation threw exception'})
});
};
And the following test:
const assert = require('assert').strict;
const target = require('./the_above_code.js');
it('should not timeout', (done) => {
target((sut) => {
console.log('continuation called');
assert.ok(false); // the test for sut.error === what I expected was failing
done();
});
});
It outputs:
promise resolved
continuation called
caught error
...
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
I realise this is because the .catch() is returning a new promise which is not resolving, but that's not what I really want during testing.
How do I test the object a promise resolves to, fail the test if necessary, have Mocha report that failure?
Perhaps there is somewhere else other than in the continuation (which never returns in the code that uses this module) that I can put the tests?
I'm sure monads can reduce the amount of boilerplate code here, but using them surely would violate Kernighan's maxim.

Mocha Chai Tests Pass buth shouldn't

Here's the current test:
describe('/POST Register Page', function() {
it('it should register new user', function(/*done*/) {
chai.request(server)
.post('/auth/register')
.send(new_user_data)
.end(function(res) {
expect(res).to.have.status(2017);
// done();
})
})
})
The last I checked, there's no http code as 2017, however, it still passes:
Registration
Get register page
GET /auth/register 200 6.989 ms - 27
✓ it should get register page
/POST Register Page
✓ it should register new user
2 passing (147ms)
I want to simply post something, then get a response back, and play with the response.
If I include the done(), I get the timeout error:
1) Registration /POST Register Page it should register new user:
Error: Timeout of 3000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
I can't get anything to work, as for whether wrong or right, the tests pass.
Although this get request is passing as expected:
describe('Get register page', function() {
it('it should get register page', function(done) {
chai.request(server)
.get('/auth/register')
.end(function(err, res) {
expect(err).to.be.null;
expect(res).to.have.status(200);
done();
})
})
})
I'm new at this mocha-cum-chai-chai-http thing, and the experience so far is weird.
Thanks.
Your POST request is probably taking longer than 3 seconds to complete, therefore mocha throws the timeout error.
You can try setting the timeout to a larger value like:
describe('/POST Register Page', function() {
// timeout in milliseconds
this.timeout(15000);
// test case
it('it should register new user', function(done) {
chai.request(server)
.post('/auth/register')
.send(new_user_data)
.end(function(res) {
expect(res).to.have.status(200);
done();
})
})
})
With some trial, you can figure out an optimum value of timeout to set in your tests.
When you don't use the done() callback, mocha simply skips the assertions without waiting for the actual response to arrive. Since the assertions in .end() block never get executed, mocha passes the test as it faces no assertions. I had faced something similar when I first started out with TDD, which I learned about the hard way.
Reference:
Because the end function is passed a callback, assertions are run
asynchronously. Therefore, a mechanism must be used to notify the
testing framework that the callback has completed. Otherwise, the test
will pass before the assertions are checked.

Observables: Complete vs finally vs done

When speaking about Observables (especially rxjs), what is the difference between "finally" and "done" or "complete"?
Finally always happens whenever an observable sequence terminates (including errors); completed only happens when it terminates without errors.
Finally:
Invokes a specified action after the source observable sequence
terminates gracefully or exceptionally.
https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/finally.md
OnCompleted:
An Observable calls this method after it has called onNext for the
final time, if it has not encountered any errors.
http://reactivex.io/documentation/observable.html
"Done" isn't an rx/observables concept. I've just seen it printed in examples of "Complete" / "OnComplete".
Note: when you call subscribe, the syntax is usually:
observable.subscribe([observer] | [onNext], [onError], [onCompleted]);
// Like this:
observable.subscribe(
(value) => { ... },
(error) => { ... },
() => { console.log('complete!'); }
);
or
observable.subscribe({
next: x => console.log('got value ' + x),
error: err => console.error('something wrong occurred: ' + err),
complete: () => console.log('done'),
});
Whereas finally is handled like this:
observable.finally(() => { console.log('finally!'); })
.subscribe(...) // you can still call subscribe
To be more precise, the finally() operator adds a dispose handler. The complete notification just calls the complete handler in observers.
What this means in practise:
When using finally() the callback is going to be called in every situation that causes unsubscription. That's when complete and error notifications are received by observers but also when you manually unsubscribe.
See demo: https://jsbin.com/dasexol/edit?js,console
complete or error handlers are called only when the appropriate notification is received. Only 0 - 1 handlers can be called but never both of them.

Running spec after promise has been resolved

I came across an issue with running a spec that should be executed after a promise has been resolved. See the commented simplified example below.
I tried using IIFE or calling done() function in the spec but none of these seemed to work.
// getIds() is a simple promise which returns an array of ids
getIds().then(function (ids) {
console.log('IDS: ' + ids); // all good so far
// This test is never run
it('dummy test', function () {
console.log('TEST HAS BEEN RUN');
});
});
You can use browser.wait() to wait until your promise is complete. Or you can put your test inside the then block:
it('should test', function() {
getIds().then(function (ids) {
// some action.
expect()...
});
});
Also, you can put the promise in a beforeEach or a beforeAll (jasmine 2). Assign the ids to a variable declared inside a describe. The value should be available for your test to use.

Resources