How to fail cypress test from inside the Promise.prototype.catch() block? - promise

I'm using a node library to execute api calls for test data setup and teardown. The library works as follows:
someApiServiceObject
.executeApiCall({... parameters})
.then(doSomethingWithResults())
.catch(() => {
// Here I would like to fail the test as something has gone wrong
})
If the request fails for some reason, I only learn about it by the Promise returning from executeApiCall function being rejected - hence the catch block.
But if I put throw new Error(); into the catch block or remove the catch block, I can see the (uncaught exception) Error: in the cypress console, but the test still passes.
Can someone advise me on how this case should be handled correctly?
The test:
it('List projects', () => {
projectsApi.projectsList({})
.then(() => {
cy.log('Success');
}).catch(() => {
throw new Error();
});
});

If you call someApiServiceObject.executeApiCall({...parameters}) in a task (since it's a node library), you should just be able to return the promise and Cypress handles failing the test. Don't catch() within the task.
module.exports = (on, config) => {
on('task', {
api(parameters) {
return someApiServiceObject
.executeApiCall({... parameters})
.then(doSomethingWithResults())
},
})
}
If that fails, follow this pattern Return number of files in the folder
module.exports = (on, config) => {
on('task', {
countFiles(folderName) {
return new Promise((resolve, reject) => {
someApiServiceObject
.executeApiCall({... parameters})
.then(doSomethingWithResults())
.then((results) => resolve(results))
.catch((err) => reject(err))
})
})
},
})
}
From comments, I think there's a assumption being made that .executeApiCall() must be returning a promise, but that may not be the case.
For example cy.get(...) has a .then(...) method, but it does not return a promise, it just has a .then() method.
If .executeApiCall() does actually return a promise, the first example is all you need. If it does not, you need to wrap the code.
Cypress will recognise a promise returned from a task, and use resolve or reject accordingly.

Related

Cypress cy.wait(...) response body is undefined despite setting up fixture in intercept command

I have created Cypress e2e tests that use the following functions:
to mock the responses
export function getUserAndSupplier(): void {
cy.intercept('GET', `${Cypress.env('BaseUrl')}/users/me`,
{
fixture: 'shared/Users/me.json',
})
.as('users');
cy.intercept('GET', `${Cypress.env('BaseUrl')}/users/me/supplier`,
{
fixture: 'shared/Suppliers/supplier.json',
})
.as('supplier');
}
to check if responses are in accordance to the fixtures:
export function checkUserAndSupplier(): void {
cy.wait('#users')
.its('response.body')
.should('not.be.undefined')
.then((interception: any) => {
//assertions on each field
});
cy.wait('#supplier')
.its('response.body')
.should('not.be.undefined')
.then((interception: GetCurrentSupplierResponse) => {
//assertions on each field
});
}
Tests have Cucumber preprocessor implemented, the GIVEN and WHEN steps definition for given test are:
beforeEach(() => {
// intercept user and supplier api
getUserAndSupplier();
// intercept GET /paymentProviders
interceptPaymentProviders();
});
Given('User navigates to the {string} page', () => {
cy.visit('/sell/payment-providers');
// assert api calls on user and supplier
checkUserAndSupplier();
});
When('User clicks on {string} button', () => {
getActivationButton()
.scrollIntoView()
.contains('Activate')
.should('be.visible')
.and('not.be.disabled')
.click();
// Ensure Continue Button is disabled
getContinueButton()
.should('be.visible')
.and('be.disabled');
});
while the .feature file test is:
Scenario: Happy path - activate payment method
Given User navigates to the "sell/payment-providers" page
When User clicks on "activate" button
Then User is able to successfully activate payment provider
The problem is that sometimes, despite having responses mocked using fixtures (they're not null or empty), 'response.body' property is undefined, which makes tests flaky.
At the beginning I thought I have some asynchronous functions that lack await and make response.body being undefined, but this was not the case.
What may be the cause of this? And what makes it sometimes working, and sometimes not?
It's hard to tell what exactly is going on, the code looks ok.
Here's some general tips to try.
Cache
It's possible the browser cache is interfering with the intercept. To avoid caching, add this
beforeEach(() => {
Cypress.automation('remote:debugger:protocol', {
command: 'Network.clearBrowserCache'
})
...
})
Debug call sequence
To debug the network calls, combine the two intercepts and use callbacks to console.log what gets intercepted.
If something is changing the order of response, the problem may be caused by the sequence of cy.wait('#users') followed by cy.wait('#supplier') so combining the intercepts will catch that.
export function getUserAndSupplier(): void {
cy.intercept('/users*', (req) => {
if (req.url.endsWith('/me')) {
console.log('users request', req)
req.alias = 'users'
req.reply({fixture: 'shared/Users/me.json'})
}
if (req.url.endsWith('/me/supplier')) {
console.log('supplier request', req)
req.alias = 'supplier'
req.reply({fixture: 'shared/Suppliers/supplier.json'})
}
})
}
Or use a single alias for both paths and check inside the interception.
export function getUserAndSupplier(): void {
cy.intercept('/users*', (req) => {
if (req.url.endsWith('/me')) {
req.reply({fixture: 'shared/Users/me.json'})
}
if (req.url.endsWith('/me/supplier')) {
req.reply({fixture: 'shared/Suppliers/supplier.json'})
}
})
.as('both')
}
export function checkUserAndSupplier(): void {
const checkInterception = (interception) => {
if (interception.request.url.endsWith('/me')) {
console.log('users response', interception.response)
// assertions for users
}
if (interception.request.url.endsWith('/me/supplier')) {
console.log('supplier response', interception.response)
// assertions for supplier
}
}
cy.wait('#both').then(checkInterception); // first interception
cy.wait('#both').then(checkInterception); // second interception
}
Lastly, something in interceptPaymentProviders() is interfering with the other intercepts.

Cypress cy.request .then chaining returning undefined

I'm upgrading Cypress from 10.2.0 to 10.11.0 and I'm encountering some behaviour I'm trying to understand.
In the second .then, the response is undefined. This had previously worked on 10.2.0.
public makeRequest(params) {
return cy.request({
...params
})
.then((response) => {
// do something with response
});
}
this.makeRequest(params)
.then((response) => {
// response is undefined
});
Can anyone point me in the right direction, I have checked the changelogs for every version since 10.3.0 and cannot find anything to explain this behaviour.
Thanks!
Cypress (version 10.11.0 and previous versions) returns the last command result taken within the cy.request().then() chain, when there is no explicit return value given.
For example, if // do some async tasks is a non-Cypress asynchronous query, the response is returned:
cy.visit('http://example.com');
function makeRequest(params) {
return cy.request(params)
.then((response) => {
// do some async tasks
setTimeout(() => {
console.log(response.title)
expect(response.body.title).to.eq('delectus aut autem') // passes
}, 1000)
})
}
makeRequest({url: 'https://jsonplaceholder.typicode.com/todos/1'})
.then(response => {
expect(response.body.title).to.eq('delectus aut autem') // passes
})
If however you issue more Cypress commands inside the // do some async tasks block, the last chained "subject" changes, and you get a different return value
cy.visit('http://example.com');
function makeRequest(params) {
return cy.request(params)
.then((response) => {
// do some async tasks
cy.get('h1') // changes "subject" from response to <h1> element
})
}
makeRequest({url: 'https://jsonplaceholder.typicode.com/todos/1'})
.then(response => {
console.log(response) // not your response, but the last "subject" found above
expect(response.text()).to.eq('Example Domain') // passes
})
Adding a return returns the response
public makeRequest(params) {
return cy.request({
...params
})
.then((response) => {
// do something with response
return response;
});
}
If you are doing some async tasks in makeRequest.then(), do your async tasks and return response in another .then. E.g:
public makeRequest(params) {
return cy.request({
...params
})
.then((response) => {
// do some async tasks
})
.then((response) => {
return response;
});
}

Mocha .then(done) doesn't work as expected

This question is not about a problem which I can't solve, it is just a curiosity. I'm not very experienced with Mocha, but there's something interesting I've stumbled upon already.
What I want is to use done() to tell Mocha the promise has been resolved.
The following code DOESN'T work:
beforeEach((done) => {
user = new User({ name: 'Dummy' })
user.save()
.then(done)
})
I know I'm passing the result of the user.save() promise to done, but I think it shouldn't be a problem.
Instead this other code works:
beforeEach((done) => {
user = new User({ name: 'Dummy' })
user.save()
.then(() => done())
})
It seems to me that Mocha done() has some kind of control flow which leads to: Error: done() invoked with non-Error: {"_id":"5b65b9d2669f7b2ec0a3d503","name":"Dummy","__v":0}
Is it because done() wants strictly an error as its argument?
Why done() does even care about what I pass to it?
Can you make some example showing why done() argument to be an Error is useful?
Thanks in advance ;)
It is because done() in Mocha only accepts Error argument. In your case, your save() method returns json object not an Error ie new Error('failed save').
If we take a look at mocha test file, we can see that it won't accept other type of arguments.
// https://github.com/mochajs/mocha/blob/master/test/unit/runnable.spec.js#L358
describe('when done() is invoked with a string', function () {
it('should invoke the callback', function (done) {
var test = new Runnable('foo', function (done) {
done('Test error'); // specify done with string/text argument
});
test.run(function (err) {
assert(err.message === 'done() invoked with non-Error: Test error');
done();
});
});
});
But if we see the test when the argument is Error, it works
// https://github.com/mochajs/mocha/blob/master/test/unit/runnable.spec.js#L345
describe('when an error is passed', function () {
it('should invoke the callback', function (done) {
var test = new Runnable('foo', function (done) {
done(new Error('fail'));
});
test.run(function (err) {
assert(err.message === 'fail');
done();
});
});
});
Btw, I suggest that you avoid using done since mocha supports promise by specifying return statement. So, we change the code into
beforeEach(() => {
user = new User({ name: 'Dummy' })
return user.save().then(user => {
// antyhing todo with user
});
});
Hope it helps.

Angular 6 unit test rxjs 6 operator tap unit test interceptor

Since I update my code to the new Rxjs 6, I had to change the interceptor code like this:
auth.interceptor.ts:
...
return next.handle(req).pipe(
tap((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
// do stuff with response if you want
}
}),
catchError((error: any) => {
if (error instanceof HttpErrorResponse) {
if (error.status === 401) {
this.authService.loginRedirect();
}
return observableThrowError(this.handleError(error));
}
})
);
and I'm not able to test the rxjs operators "tap" and "catchError".
Actually i'm only able to test if pipe is called:
it('should intercept and handle request', () => {
const req: any = {
clone: jasmine.createSpy('clone')
};
const next: any = {
handle: () => next,
pipe: () => next
};
spyOn(next, 'handle').and.callThrough();
spyOn(next, 'pipe').and.callThrough();
interceptor.intercept(req, next);
expect(next.handle).toHaveBeenCalled();
expect(next.pipe).toHaveBeenCalled();
expect(req.clone).toHaveBeenCalled();
});
Any help is apreciated on how to spy the rxjs operators
I think the problem is that you shouldn't be testing that operators were called like this at the first place.
Operators in both RxJS 5 and RxJS 6 are just functions that only "make recipe" how the chain is constructed. This means that checking if tap or catchError were called doesn't tell you anything about it's functionality or whether the chain works at all (it might throw an exception on any value and you won't be able to test it).
Since you're using RxJS 6 you should rather test the chain by sending values through. This is well documented and pretty easy to do https://github.com/ReactiveX/rxjs/blob/master/doc/marble-testing.md
In your case you could do something like this:
const testScheduler = new TestScheduler((actual, expected) => {
// some how assert the two objects are equal
// e.g. with chai `expect(actual).deep.equal(expected)`
});
// This test will actually run *synchronously*
testScheduler.run(({ cold }) => {
const next = {
handle: () => cold('-a-b-c--------|'),
};
const output = interceptor.intercept(null, next);
const expected = ' ----------c---|'; // or whatever your interceptor does
expectObservable(output).toBe(expected);
});
I think you'll get the point what this does...

Back-end tests with elasticsearch fails without setTimeout

I am writing tests for back-end which uses MongoDB and Elasticsearch. The problem is that without wrapping test with setTimeout test fails, and it looks like elasticsearch can't index mock data into db before test. Here is the code:
let elasticSearch = require('elasticsearch');
let elasticClient = new elasticSearch.Client({
host: 'localhost:9200'
});
let app = require('./dist/app'); //path to my application
let supertest = require('supertest');
before((done) => {
elasticClient.index(elasticMockData, function() {
done();
});
});
beforeEach(() => {
request = supertest(app);
});
it('should get data from elastic', () => {
setTimeout(() => { // if I comment this timeout, test will fail
request.get('/api/elastic')
.end((err, res) => {
expect(res.body.hits.hits.length).not.to.equal(0);
})
}, 1000); // if I comment this timeout, test will fail
});
I think you will agree that timeout is not an elegant and nice solution, which slows every test to 1 second or more. Maybe, am I missing something?
Found a solution, maybe it will be useful for someone.
According to Elasticsearch docs:
By default, the document will be available for get() actions immediately, but will only be available for searching after an index refresh (which can happen automatically or manually).
So, in this case, done() should be called within another callback function:
before((done) => {
elasticClient.index(elasticMockData, function() {
elasticClient.indices.refresh(function (err: any, res: any) {
if (err) {
return;
}
done();
});
});
});

Resources