How to test global middlware in expressJS? - express

This seems like a simple question, but I'm stumped. How do I test my expressjs middleware is setting up the user session properly?
app.use((req:Req, res:Response, next:NextFunction) => {
req.userSessionId = uuid();
next();
});
I am not sure how to test that an incoming request is being captured and setting the userSessionId correctly. I want something like
describe('setting up middleware', ()=> {
//instantiate the app
expect(app.use).tobeCalled();
expect(mockReq).toBeSetWith(userSessionId)
});

You can refactor your middleware code into a function, and write unit-tests that test this function logic.
Your unit tests, in my opinion (and others), should only test your code and avoid testing Express logic (calling your middleware). For this you should have integration tests which test your app is working properly as a whole
// This middleware code needs to be tested
const userSessionMiddleare = (req: Request, res:Response, next: NextFunction) => {
req.userSessionId = uuid();
next();
};
// This Express logic should not be unit-tested but rather tested in your integration testing
app.use(userSessionMiddleare);

Related

cypress cy.request 401 unauthorized [duplicate]

I want to save/persist/preserve a cookie or localStorage token that is set by a cy.request(), so that I don't have to use a custom command to login on every test. This should work for tokens like jwt (json web tokens) that are stored in the client's localStorage.
To update this thread, there is already a better solution available for preserving cookies (by #bkucera); but now there is a workaround available now to save and restore local storage between the tests (in case needed). I recently faced this issue; and found this solution working.
This solution is by using helper commands and consuming them inside the tests,
Inside - cypress/support/<some_command>.js
let LOCAL_STORAGE_MEMORY = {};
Cypress.Commands.add("saveLocalStorage", () => {
Object.keys(localStorage).forEach(key => {
LOCAL_STORAGE_MEMORY[key] = localStorage[key];
});
});
Cypress.Commands.add("restoreLocalStorage", () => {
Object.keys(LOCAL_STORAGE_MEMORY).forEach(key => {
localStorage.setItem(key, LOCAL_STORAGE_MEMORY[key]);
});
});
Then in test,
beforeEach(() => {
cy.restoreLocalStorage();
});
afterEach(() => {
cy.saveLocalStorage();
});
Reference: https://github.com/cypress-io/cypress/issues/461#issuecomment-392070888
From the Cypress docs
For persisting cookies: By default, Cypress automatically clears all cookies before each test to prevent state from building up.
You can configure specific cookies to be preserved across tests using the Cypress.Cookies api:
// now any cookie with the name 'session_id' will
// not be cleared before each test runs
Cypress.Cookies.defaults({
preserve: "session_id"
})
NOTE: Before Cypress v5.0 the configuration key is "whitelist", not "preserve".
For persisting localStorage: It's not built in ATM, but you can achieve it manually right now because the method thats clear local storage is publicly exposed as Cypress.LocalStorage.clear.
You can backup this method and override it based on the keys sent in.
const clear = Cypress.LocalStorage.clear
Cypress.LocalStorage.clear = function (keys, ls, rs) {
// do something with the keys here
if (keys) {
return clear.apply(this, arguments)
}
}
You can add your own login command to Cypress, and use the cypress-localstorage-commands package to persist localStorage between tests.
In support/commands:
import "cypress-localstorage-commands";
Cypress.Commands.add('loginAs', (UserEmail, UserPwd) => {
cy.request({
method: 'POST',
url: "/loginWithToken",
body: {
user: {
email: UserEmail,
password: UserPwd,
}
}
})
.its('body')
.then((body) => {
cy.setLocalStorage("accessToken", body.accessToken);
cy.setLocalStorage("refreshToken", body.refreshToken);
});
});
Inside your tests:
describe("when user FOO is logged in", ()=> {
before(() => {
cy.loginAs("foo#foo.com", "fooPassword");
cy.saveLocalStorage();
});
beforeEach(() => {
cy.visit("/your-private-page");
cy.restoreLocalStorage();
});
it('should exist accessToken in localStorage', () => {
cy.getLocalStorage("accessToken").should("exist");
});
it('should exist refreshToken in localStorage', () => {
cy.getLocalStorage("refreshToken").should("exist");
});
});
Here is the solution that worked for me:
Cypress.LocalStorage.clear = function (keys, ls, rs) {
return;
before(() => {
LocalStorage.clear();
Login();
})
Control of cookie clearing is supported by Cypress: https://docs.cypress.io/api/cypress-api/cookies.html
I'm not sure about local storage, but for cookies, I ended up doing the following to store all cookies between tests once.
beforeEach(function () {
cy.getCookies().then(cookies => {
const namesOfCookies = cookies.map(c => c.name)
Cypress.Cookies.preserveOnce(...namesOfCookies)
})
})
According to the documentation, Cypress.Cookies.defaults will maintain the changes for every test run after that. In my opinion, this is not ideal as this increases test suite coupling.
I added a more robust response in this Cypress issue: https://github.com/cypress-io/cypress/issues/959#issuecomment-828077512
I know this is an old question but wanted to share my solution either way in case someone needs it.
For keeping a google token cookie, there is a library called
cypress-social-login. It seems to have other OAuth providers as a milestone.
It's recommended by the cypress team and can be found on the cypress plugin page.
https://github.com/lirantal/cypress-social-logins
This Cypress library makes it possible to perform third-party logins
(think oauth) for services such as GitHub, Google or Facebook.
It does so by delegating the login process to a puppeteer flow that
performs the login and returns the cookies for the application under
test so they can be set by the calling Cypress flow for the duration
of the test.
I can see suggestions to use whitelist. But it does not seem to work during cypress run.
Tried below methods in before() and beforeEach() respectively:
Cypress.Cookies.defaults({
whitelist: "token"
})
and
Cypress.Cookies.preserveOnce('token');
But none seemed to work. But either method working fine while cypress open i.e. GUI mode. Any ideas where I am coming short?
2023 Updated on Cypress v12 or more:
Since Cypress Version 12 you can use the new cy.session()
it cache and restore cookies, localStorage, and sessionStorage (i.e. session data) in order to recreate a consistent browser context between tests.
Here's how to use it
// Caching session when logging in via page visit
cy.session(name, () => {
cy.visit('/login')
cy.get('[data-test=name]').type(name)
cy.get('[data-test=password]').type('s3cr3t')
cy.get('form').contains('Log In').click()
cy.url().should('contain', '/login-successful')
})

Expo secure-store unavailable in jest tests

This question is specifically about expo-secure-store and jest.
Currently, I am using expo-secure-store to store my JWT when logging in. It works fine when running on emulator however, it doesn't work at all in Jest tests; token comes back as undefined. I am able to call the functions like normal.
Excuse any typos I might have made refactoring this.
Calling from tests:
test('when logging in, given correct credentials, gets response token.', async () => {
try {
var token = await SecureStore.getItemAsync("token");
await SecureStore.setItemAsync('token', 'test');
token = await SecureStore.getItemAsync('token');
console.log(token);
expect(token).toBeDefined();
expect(token).toBe("test");
} catch (err) {
console.log(err);
throw err;
}
}
Question: Does expo-secure-store not load/work without an actual device/emulator?
There are no documentation at all about testing with secure-store and from what I can tell I might have to mock this module.
you have to mock any native module that you use because their implementations live primarily in native code that is intended to be run on ios and android devices. so the same applies here - you should mock expo-secure-store.

How to define a Role without login page in TestCafe?

I'm having trouble to make my tests stable with this code.
Can someone give me some directions
My app doesn't have a login page, and users might get logged in different ways, what determines if they're clogged or not is an auth cookie.
So I decided to define a role by a direct API call to the authentication API then save a cookie with the user token:
export const adminUser = Role('any-page', async t => {
const loginRequest = await fetch(
`https://my-auhtentication-api/oauth/token?grant_type=password&username=${userName}&password=${password}`,
{
method: 'POST',
}
)
const loginToken = await loginRequest.json()
await ClientFunction(() => {
document.cookie = `myAuthCookie=${loginToken.access_token}; Domain=.my-app-domain.com; Path=/`
})
})
And then I use in my code test like this:
fixture('[Admin User] Menu navigation')
.beforeEach(async t => {
await t
.useRole(adminUser)
.navigateTo(strictPage)
})
The problem is that it works fine when I run only this test, but when I run it with the rest of my stack it becomes very unstable, sometimes works, sometimes not.
For end-to-end tests, it's better if your test environment is the same as your production environment. Your comment indicates that you use a parent web app to authenticate your users in the production environment, so the best way to test authentication is using the same parent app in your test environment.

Prevent supertest from running until express server has started

I have an node / express js app that was generated using the yoman full stack generator. I have swapped out mongo / mongoose for cloudant db (which is just a paid for version of couchdb). I have a written a wrapper for the Cloudant node.js library which handles cookie auth with my instance via an init() method wrapped in a promise. I have refactored my application to not start the express server until the connection to the db has been established as per snippet below taken from my app.js
myDb.init(config).then(function (db) {
logger.write(1001, '','Connection to Cloudant Established');
// Start server
server.listen(config.port, config.ip, function () {
logger.write(1001, "",'Express server listening on '+config.port+', in '+app.get('env')+' mode');
});
});
On my express routes I have introduced a new middleware which attaches the db object to the request for use across the middleware chain as per below. This gets the db connection object before setting the two collections to use.
exports.beforeAll = function (req, res, next) {
req.my = {};
// Adding my-db
req.my.db = {};
req.my.db.connection = myDb.getDbConnection();
req.my.db.orders = req.my.db.connection.use(dbOrders);
req.my.db.dbRefData = req.my.db.connection.use(dbRefData);
next();
};
This mechanism works when i manually drive my apis through POSTman as the express server won't start until after the promise from the db connection has been resolved. However when running my automated tests the first few tests are now always failing because the application has not finished initialising with the db before jasmine starts to run my tests against the APIs. I can see in my logs the requests on the coming through and myDb.getDbConnection(); in the middleware returning undefined. I am using supertest and node-jasmine to run my tests. For example
'use strict';
var app = require('../../app');
var request = require('supertest');
describe('GET /api/content', function () {
it('should respond with JSON object', function (done) {
request(app)
.get('/api/content')
.expect(200)
.expect('Content-Type', /json/)
.end(function (err, res) {
if (err) return done(err);
expect(res.body).toEqual(jasmine.any(Object));
done();
});
});
});
So, my question is how can I prevent supertest from making the requests until the server.listen() step has been completed as a result of the myDb.init() call being resolved? OR perhaps there is some kind of jasmine beforeAll that I can use to stop it running the describes until the promise has been resolved?
You could make you app return an EventEmitter which emits a "ready" event when it has completed its initialisation.
Then your test code, in a before clause, can wait until the "ready" event arrives from the app before proceeding with the tests.

How to test promises in Mongo(ose)/Express app?

I'm using promises to wrap asynchronous (Mongo) DB ops at the end of an (expressJS) route.
I want to try and figure out how to test the following code.
userService
userService.findOne = function (id) {
var deferred = q.defer();
User.findOne({"_id" : id})
.exec(function (error, user) {
if (error) {
deferred.reject(error);
} else {
deferred.resolve(user);
}
});
return deferred.promise;
};
userRoute
var user = function (req, res) {
var userId = req.params.id
, userService = req.load("userService");
// custom middleware that enables me to inject mocks
return userService.findOne(id)
.then(function (user) {
console.log("called then");
res.json({
msg: "foo"
});
}).catch(function (error) {
console.log("called catch");
res.json({
error: error
});
}).done();
};
Here's an attempt to test the above with mocha
userTest
it("when resolved", function (done) {
var jsonSpy = sinon.spy(httpMock.res, "json")
, httpMock = require("/path/to/mock/http/object")
, serviceMock = require("/path/to/mock/service"),
, deferred = q.defer()
, findStub = sinon.stub(serviceMock, "findOne")
.returns(deferred.promise)
, loadStub = sinon.stub(httpMock.req, "load")
.returns(serviceMock),
retPromise;
// trigger route
routes.user(httpMock.req, httpMock.res);
// force promise to resolve?
deferred.resolve();
expect(jsonSpy.called).to.be.true; // fails
// chai as promised
retPromise = findStub.returnValues[0];
expect(retPromise).to.be.fulfilled; // passes
});
the http mock is just an empty object with no-ops where expressJS would normally start rendering stuff. I've added some logging inside those no-ops to get an idea on how this is hanging together.
This isn't really working out. I want to verify how the whole is integrated, to establish some sort of regression suite - but I've effectively mocked it to smithereens and I'm just testing my mocks (not entirely successfully at that).
I'm also noticing that the console logs inside my http mocks triggered by then and catch are firing twice - but the jsonSpy that is invoked inside the actual code (verified by logging out the sinon spy within the userRoute code) is not called in test.
Has anyone got some advice on integration testing strategies for express apps backed by Mongo?
It looks to me like you're not giving your promise an opportunity to fire before you check if the result has been called. You need to wait asynchronously for userService.findOne()'s promise chain to complete before jsonSpy.called will be set. Try this instead:
// start of code as normal
q.when(
routes.user(httpMock.req, httpMock.res),
function() { expect(jsonSpy.called).to.be.true; }
);
deferred.resolve();
// rest of code as normal
That should chain off the routes.user() promise and pass as expected.
One word of caution: I'm not familiar with your framework, so I don't know if it will wait patiently for all async events to go off. If it's giving you problems calling back into your defer chain, you may want to try nodeunit instead, which handles async tests very well (IMO).