mocha tests Is it possible to reuse the same before() and after() hooks in all my tests? - mocha.js

I use the same before() and after() root level hooks in all my tests to setup and clear my test-database ...
Is there any way to move them into a file and export/import that file ?

Yes. I was able to achieve this behavior by taking advantage of mocha's this context, discussed in their Wiki article about shared behaviors. I am using ts-mocha which accounts for the async/await functionality.
So I wrote functions to login and logout using supertest-session and it looks something like this:
export function authHooks()
{
const email = UsersSeed[0].email;
const password = UsersSeed[0].password;
before(`login ${email}`, async function()
{
await this.session.post('/api/account/login')
.send({ email, password })
.expect(201);
});
after(`logout ${email}`, async function()
{
await this.session.get('/api/account/logout')
.expect(200);
});
}
Inside of my describe I set up the this.session and in my it-statements I can reuse it. It looks a little like this:
import { expect } from 'chai';
import * as Session from 'supertest-session';
import { authHooks } from './authHooks';
describe('Some Suite', () =>
{
before(function()
{
this.session = new Session(SomeApp);
});
after(function()
{
this.session.destroy();
});
describe('when not authenticated', () =>
{
it('returns 403', async function()
{
await this.session.get('/api/jobs')
.expect(403)
.expect({ statusCode: 403, message: 'Forbidden resource' });
});
});
describe('when authenticated', () =>
{
authHooks();
describe('when finding jobs', () =>
{
it('returns all jobs', async function()
{
await this.session.get('/api/jobs')
.expect(200)
.then((response) =>
{
expect(response.body).to.deep.eq(SomeThing);
});
});
});
});
});
I'm not sure if this is the best way to achieve it (I'm not a fan of using function over () => {} personally), but I have confirmed it works.
The above code will not run mainly because I'm protecting code specifics, but hopefully this will provide at least one option for you and maybe someone else can show a better way to do this.

Related

Angular jasmine how to spy an async spy method

I am new to writing test with async/await in angular.
I have the following code. The service method is an async method. The test fails saying component.options.length is 0.
Can anyone please help me how to fix the error so the options has got the value i set in spy?
Thanks
spec.ts
spySideNavService = jasmine.createSpyObj('SideNavService', [], {
setOrgUserDetails: () => {},
loadMenus: () =>
[
{
id: 'my-menu',
label: 'My Menu',
icon: 'far fa-envelope fa-2x',
url: 'url'
}
] as NavOption[]
});
describe('ngOnInit', () => {
it('should add navigation options', () => {
expect(component.options.length).toBeGreaterThan(0);
});
});
component:
ngOnInit(): void {
this.options = await this.sideNavService.loadMenus();
}
SideNavService:
async loadMenus(): Promise<NavOption[]> {
//logic
}
Tried answer given below but still not working:
describe('ngOnInit', () => {
it('should add navigation options', fakeAsync(() => {
// !! call tick(); to tell the test to resolve all promises
// before coming to my expect line
tick();
expect(component.options.length).toBeGreaterThan(0);
}));
});
You need to use fakeAsync/tick to control promises.
// !! add fakeAsync
it('should add navigation options', fakeAsync(() => {
// !! call tick(); to tell the test to resolve all promises
// before coming to my expect line
// !! call ngOnInit
component.ngOnInit();
console.log(component.options);
tick();
console.log(component.options);
expect(component.options.length).toBeGreaterThan(0);
}));
Before, the test would go to the await line and go back to the test for the expect because the await is saying to do this later. Now with the tick, we are saying if they are any promises created, resolve them before moving forward.
Also, I think you're missing a Promise.resolve on loadMenus.
loadMenus: () => Promise.resolve(
[
{
id: 'my-menu',
label: 'My Menu',
icon: 'far fa-envelope fa-2x',
url: 'url'
}
] as NavOption[])
I am thinking the Promise.resolve is required so it can be awaited.
edit
I don't think the done callback will help you.
You can try using await fixture.whenStable() to wait for the promise(s). Try this:
describe('ngOnInit', () => {
it('should add navigation options', async () => {
component.ngOnInit();
await fixture.whenStable();
expect(component.options.length).toBeGreaterThan(0);
});
});

Cypress: Why finicky in handling spec's after hook?

Blocked by a Cypress custom command not executing properly within a spec's after hook when a test failed. Experimented with the following minimal spec examples.
Context
Testing two Cypress custom commands:
cy.cmdLogin(): Standard username password authentication
cy.cmdLogout(): cy.get('logout_button').click()
Spec #1, cy.cmdLogout() in after hook does work
Very simple Cypress tests spec, test cy.cmdLogin() with after hook with cy.cmdLogout(), works perfectly:
context('SPEC Login', () => {
after('AFTER Logout', () => {
if (this.successLogin) {
cy.cmdLogout().then(() => {
cy.log('Logout Success');
});
}
});
it('TEST Login', () => {
cy.cmdLogin().then(() => {
cy.log('Login Success');
this.successLogin = true;
});
});
});
Spec #2, cy.cmdLogout() in after hook does not work
Modified Cypress tests spec, test cy.cmdLogin() with after hook with cy.cmdLogout(), now added a test that forced failure expect(false).to.be.a('boolean').to.be.true;. The after hook cy.cmdLogout() is called and fails:
Verified that Cypress could handle cy.get('logout_button') without failure.
Yet, extending with .click(), cy.get('logout_button').click(), Cypress would throw an error: Can not perform click() on undefined.
context('SPEC Login', () => {
after('AFTER Logout', () => {
if (this.successLogin) {
cy.cmdLogout().then(() => {
cy.log('Logout Success');
});
}
});
it('TEST Login', () => {
cy.cmdLogin().then(() => {
cy.log('Login Success');
this.successLogin = true;
});
});
it('TEST Fail', () => {
expect(false).to.be.a('boolean').to.be.true;
});
});
Spec #3, removed after hook, cy.cmdLogout() moved to another test does work
Modified Cypress tests spec again and removed after hook, test cy.cmdLogin(), test forced failure, and now test cy.cmdLogout(). This works perfectly:
context('SPEC Login', () => {
it('TEST Login', () => {
cy.cmdLogin().then(() => {
cy.log('Login Success');
this.successLogin = true;
});
});
it('TEST Fail', () => {
expect(false).to.be.a('boolean').to.be.true;
});
it('TEST Logout', () => {
if (this.successLogin) {
cy.cmdLogout().then(()=> {
cy.task('log', 'Logout Success');
});
}
});
});
Insight as to why Cypress is finicky with spec's after hook would be very much appreciated
The short answer (for the general situation) is change after() hook to before() hook, but in your case that wouldn't work because this.successLogin will always be undefined inside a before.
You would need to create a cy.isLoggedIn() command or call cy.cmdLogout() unconditionally.
To get after() the logic working on a fail, use
after('AFTER Logout', () => {
cy.cmdLogout()
});
Cypress.on('fail', (error, runnable) => {
cy.cmdLogout()
throw error
})
which is equivalent to this in plain javascript
try {
some-code-that-might-error()
cy.cmdLogout() // does not run if error occurs
} catch (error) {
cy.cmdLogout() // do the logout in error situation
}

Cypress sharing variables/alias between Hooks?

So I have a pretty `before` and `beforeEach` function that runs before all tests. It looks something like this:
describe("JWT Authentication", function() {
before(function() {
// custom command runs once to get token for JWT auth
// alias token as 'user' for further use
cy.get_auth_token().as('user')
})
beforeEach(function() {
// before each page load, set the JWT to the aliased 'user' token
cy.visit("/", {
onBeforeLoad(win) {
// set the user object in local storage
win.localStorage.setItem("token", this.user.token);
}
})
})
it("a single test...", function() {
//do stuff
});
The custom command is also pretty simple:
Cypress.Commands.add("get_auth_token", () => {
cy.request("POST", Cypress.env("auth_url"), {
username: Cypress.env("auth_username"),
password: Cypress.env("auth_password")
})
.its("body")
.then(res => {
return res;
});
})
The custom command itself works and retrieves the token as expected. However when it comes to the beforeEach it has no idea what this.user.token is. Specifically not knowing what user is.
One option is of course calling the command in every beforeEach which is what the JWT Cypress recipe/example spec does. However this feels excessive because in my case I do not NEED to grab the token every test. I only need to grab it once for this set of tests.
So how can I share the token to the beforeEach hook with a Cypress custom command.
I ran a few tests, all the bits seem to work!
The following does not give you an answer, but may help you debug.
Passing token between before() and beforeEach()
Assume we have a user in before(), does it get to the onBeforeLoad() callback?
describe("JWT Authentication", function() {
before(function() {
const mockUser = { token: 'xyz' };
cy.wrap(mockUser).as('user');
})
beforeEach(function() {
cy.visit("http://example.com", {
onBeforeLoad(win) {
console.log(this.user.title); // prints 'xyz'
}
})
})
it("a single test...", function() {
//do stuff
})
});
Is the custom command working
I can't find a generic mock for an Auth check, but any cy.request() that gets an object should be equivalent.
I'm hitting typicode.com and looking for the title property
describe("JWT Authentication", function() {
Cypress.Commands.add("get_auth_token", () => {
cy.request("GET", 'https://jsonplaceholder.typicode.com/todos/1')
.its("body")
.then(body => {
console.log('body', body); // prints {userId: 1, id: 1, title: "delectus aut autem", completed: false}
return body;
});
})
before(function() {
cy.get_auth_token()
.then(user => console.log('user', user)) // prints {userId: 1, id: 1, title: "delectus aut autem", completed: false}
.as('user')
})
beforeEach(function() {
cy.visit("http://example.com", {
onBeforeLoad(win) {
console.log(this.user.title); // prints 'delectus aut autem'
}
})
})
it("a single test...", function() {
//do stuff
})
});
Custom command
This shorter version also seems to work
Cypress.Commands.add("get_auth_token", () => {
cy.request("GET", 'https://jsonplaceholder.typicode.com/todos/1')
.its("body");
})

having multiple describe in spec leads to weird behaviour

I have 2 describe blocks in a spec file.
First, describe visits xyz.com and Second, describe visits abc.com
And I need these 2 describe in one spec only. The wired behavior I see is it runs the tests smoothly but after visiting abc.com from 2nd describe it starts running 1st describe again. An infinite loop of tests
var signedOutArtifactID = null;
describe('WEB APP E2E tests', function() {
var token = null;
before(function() {
cy.visit('/');
// Login
cy.get('#username')
.type(auth.geneticist.username);
cy.get('#password')
.type(auth.geneticist.password);
cy.get('button')
.contains('Login')
.click()
.should(function() {
token = localStorage.getItem('token');
expect(token).not.to.be.null;
});
});
beforeEach(function() {
localStorage.setItem('token', token);
cy.contains('Logout')
.should('exist');
expect(localStorage.getItem('token'));
});
it('should land on home page', function() {
cy.url()
.should('include', '/home');
});
it('should save and generate and end up on signout page', function() {
cy.contains('Save and Generate Report')
.click();
cy.url()
.should('include', '/sign-out');
});
it('should signout and send successfully', function() {
cy.url()
.should(function(currentURL) {
signedOutArtifactID = currentURL.match(/2-([0-9]+)/)[0];
expect(signedOutArtifactID).not.to.be.null;
});
// Make sure interpretation was updated
cy.get('.card-body pre')
.should('contain', 'test interpretation added by cypress');
cy.contains('Sign Out and Send')
.click();
cy.contains('Yes, sign out and send')
.click();
});
});
describe('2nd WEB APP E2E tests', function() {
before(function () {
cy.visit({
url:`https://webappurl.com/search?scope=All&query=${signedOutArtifactID}`,
failOnStatusCode: false
})
})
it('Review Completed step in clarity', async () => {
cy.get('#username').type(auth.clarity_creds.username)
cy.get('#password').type(auth.clarity_creds.password)
cy.get('#sign-in').click()
cy.get('.result-name').click()
cy.get('.view-work-link').contains('QWERTYU-IDS').click()
cy.get('.download-file-link ')
.should(($downloads) => {
expect($downloads).to.have.length(2)
})
});
});
describe defines a test suite. You can only have one top-level test suite per file, and only one domain per test.
I would just change your describes to contexts and wrap both contexts in a single describe, like so:
var signedOutArtifactID = null;
describe('e2e tests', function() {
context('WEB APP E2E tests', function() {
var token = null;
before(function() {
cy.visit('/');
// Login
cy.get('#username')
.type(auth.geneticist.username);
cy.get('#password')
.type(auth.geneticist.password);
cy.get('button')
.contains('Login')
.click()
.should(function() {
token = localStorage.getItem('token');
expect(token).not.to.be.null;
});
});
beforeEach(function() {
localStorage.setItem('token', token);
cy.contains('Logout')
.should('exist');
expect(localStorage.getItem('token'));
});
it('should land on home page', function() {
cy.url()
.should('include', '/home');
});
it('should save and generate and end up on signout page', function() {
cy.contains('Save and Generate Report')
.click();
cy.url()
.should('include', '/sign-out');
});
it('should signout and send successfully', function() {
cy.url()
.should(function(currentURL) {
signedOutArtifactID = currentURL.match(/2-([0-9]+)/)[0];
expect(signedOutArtifactID).not.to.be.null;
});
// Make sure interpretation was updated
cy.get('.card-body pre')
.should('contain', 'test interpretation added by cypress');
cy.contains('Sign Out and Send')
.click();
cy.contains('Yes, sign out and send')
.click();
});
});
context('2nd WEB APP E2E tests', function() {
before(function () {
cy.visit({
url:`https://webappurl.com/search?scope=All&query=${signedOutArtifactID}`,
failOnStatusCode: false
})
})
it('Review Completed step in clarity', async () => {
cy.get('#username').type(auth.clarity_creds.username)
cy.get('#password').type(auth.clarity_creds.password)
cy.get('#sign-in').click()
cy.get('.result-name').click()
cy.get('.view-work-link').contains('QWERTYU-IDS').click()
cy.get('.download-file-link ')
.should(($downloads) => {
expect($downloads).to.have.length(2)
})
});
});
})
There should be one describe block per suite (specification file). Therefore, when I need to wrap multiple related tests in one specification file I use context. Also, the following is what cypress documentation says:
The test interface, borrowed from Mocha, provides describe(),
context(), it() and specify().
context() is identical to describe() and specify() is identical to
it(), so choose whatever terminology works best for you
However, I believe the test structure and describe, context, it hierarchy is a little off track. So, here is how I write tests:
describe('User Authentication Using Custom Auth Token', () => {
beforeEach(() => {
cy.on('uncaught:exception', err => {
console.log('cypress has detected uncaught exception', err);
return false;
});
});
context('when not authenticated', () => {
it('Redirects to /login and Stays on /Login', () => {
cy.visit('/');
cy.location('pathname').should('equal', '/login');
// more on your logic
});
context('when authenticated', () => {
it('Successful login using Custom Auth Token', ()=>{
cy.visit('/')
cy.login();
// more on your logic
});
});
});

How to test redux-thunk middleware async functions?

I'm trying to test my asyn thunk middleware function using mocha, chai and sinon (my first time!).
Please consider my files:
ayncActionCreators.js
export const fetchCurrentUser = () => {
return (dispatch) => {
setTimeout(dispatch, 100);
}
};
ayncActionCreators.spec.js
//...
it('Should work', () => {
const dispatch = sinon.spy();
const action = fetchCurrentUser();
action(dispatch);
expect(dispatch.called).to.be.true;
});
I did not yet implement the fetchCurrentUser function - just assumed it will take some "server" time and then it will call 'dispatch()'.
The spec fails, due to the async flow. If I add a setTimeout of 101 ms before the expect - it passes.
My code will use some DB API that returns promise, so the async function will eventually look like:
//...
return (dispatch) => {
return dbAPI.fetchUser().then(dispatch(....));
}
So I tried to require dbAPI and create a sinon.stub().returns(Promise.resolve()) inside the test and it didn't work as well (I thought that since the stub returns a resolved promise - the async function will act like a synchronous function).
Any ideas how should I test async functions like that?
Thank,
Amit.
Don't mock dispatch with sinon, write your own and call Mocha's done() in that when it's done.
it('Should work', (done) => {
const dispatch = () => {
// Do your tests here
done();
};
const action = fetchCurrentUser();
action(dispatch)
// Also allow quick failures if your promise fails
.catch(done);
})
If you're just wanting to ensure that the dispatch is called, then mocha will time out. The catch on the returned promise from your async action creator allows errors to be shown in the right place and for the test to fail rather than time out.
Well, I think I've found a solution:
Assuming my async function looks like this:
//...
return (dispatch) => {
return dbAPI.fetchUser().then(dispatch(....));
}
Then I can write the spec as follows:
it('Should work', () => {
dbAPI.fetchUser = sinon.stub().returns(Promise.resolve({username: 'John'}));
const dispatch = sinon.spy();
const action = fetchCurrentUser();
action(dispatch).then(() => {
expect(dispatch.called).to.be.true;
});
});
I don't know if this is a workaround or not, but it works. I would appreciate your opinions of a better way of doing this...
Thanks,
Amit.

Resources