I am trying to write tests for my Typescript Node.js application. I am using Mocha framework and Chai assertion library. Everything was working fine until custom middlewares(like authentication checking) were added. I tried using Sinon.JS to mock calls to this middleware, but I am having troubles to get it working. Any help would be appreciated.
My app.ts file looks as following:
class App {
public express: express.Application;
constructor() {
this.express = express();
this.routeConfig();
}
private routeConfig(): void {
CustomRouteConfig.init(this.express);
}
}
CustomRouteConfig file:
export default class CustomRouteConfig {
static init(app: express.Application) {
app.use('/:something', SomethingMiddleware);
app.use('/:something', SomeAuthenticationMiddleware);
app.use('/:something/route1/endpointOne', ControllerToTest);
app.use(NotFoundHandler);
app.use(ErrorHandler);
}
}
My ControllerToTest.ts file looks as following:
export class ControllerToTest {
router : Router;
constructor() {
this.router = Router();
this.registerRoutes();
}
public getData(req: Request, res: Response, next: NextFunction) {
//some logic to call Restful API and return data
}
private registerRoutes() {
this.router.get('/', this.getData.bind(this));
}
}
export default new ControllerToTest().router;
My SomethingMiddleware looks as following:
export class SomethingMiddleware {
something = (req: Request, res: Response, next: NextFunction) => {
//some logic to check if request is valid, and call next function for either valid or error situation
}
}
export default new SomethingMiddleware().something;
My test file for this scenario looks as following:
describe('baseRoute', () => {
it('should be json', () => {
return chai.request(app).get('/something/route1/endPointOne')
.then(res => {
expect(res.type).to.eql('application/json');
});
});
});
What is the best way to use Sinon.JS mocks or stubs in this situation? Also, if you think there is a better approach to write tests for this scenario, that would be appreciated as well.
I ended up using sinon stubs. I combined these with Mocha's before and after functions to control what happens when during testing some of the external libraries are called. Also, I have used this on database calls and middleware checks, as these are not of interest to my unit testing. Below is an example of how I did it.
describe("ControllerToTest", () => {
// Sinon stubs for mocking API calls
before(function () {
let databaseCallStub= sinon.stub(DatabaseClass, "methodOfInterest", () => {
return "some dummy content for db call";
});
let customMiddlewareStub= sinon.stub(MyCustomMiddleware, "customMiddlewareCheckMethod", () => {
return true;
});
let someExternalLibraryCall= sinon.stub(ExternalLibrary, "methodInExternalLibrary", () => {
return "some dummy data"
});
});
after(function () {
sinon.restore(DatabaseClass.methodOfInterest);
sinon.restore(MyCustomMiddleware.customMiddlewareCheckMethod);
sinon.restore(ExternalLibrary.methodInExternalLibrary);
});
it("should return data", () => {
return chai.request(app).get('/something/route1/endPointOne')
.then(res => {
expect(res.type).to.eql('application/json');
});
});
Related
I needed a separate validation logic per version (prefix)
const routes = require('./routes/v1/users')
fastify.register(route, { prefix: `/v1` }) // currently using fastify#4.5.2
Might be good if there's something similar to below:
fastify.register(route, {
prefix: `/v1`,
preValidation: (req, reply) => { /* logic here */ }
})
Tried looking fastify docs, other channels and I can't find any similar implementation.
There is not such option to the fastify.register method, but you can do it playing with encapsulation.
You can create a dedicated context that includes your routes + the hook.
This context will be isolated by other context you will create with register.
This example show you how:
const fastify = require('fastify')({ logger: false, exposeHeadRoutes: false })
async function run() {
const route = function routesPlugin(fastify, options, next) {
fastify.get('/hello', async (request, reply) => {
console.log('request')
return { hello: 'world' }
})
next()
}
fastify.register(function intermediatePlugin(instance, opts, next) {
instance.addHook('preValidation', function hook(request, reply, done) {
console.log('only for v1')
done()
})
instance.register(route, { prefix: `/v1` })
next()
})
fastify.register(route, { prefix: `/v2` })
await fastify.inject('/v1/hello')
await fastify.inject('/v2/hello')
}
run()
You can visualize this structure by using fastify-overview (and the fastify-overview-ui plugin together:
Note that the hook is child of the intermediatePlugin that is not shared by the v2's routesPlugin sibling.
I'm using mocha to test a function and I'm facing an error while running the test file.
The file structure is as follows
server
|-test
| |-customer.test.ts
|-customer.js
This is the customer.js file function
module.exports = (instance) => {
instance.validate = async (ctx) => {
// some code here
}
}
This is the mocha test case file customer.test.ts
const instance = require("../customer")
/* eslint-disable no-undef */
describe('customer', () => {
describe('/POST customers', () => {
it('Create Buy customer', (done) => {
instance.validate({
})
done();
});
})
});
But when I run the file using the command mocha .\customer.test.ts it shows me the following error
TypeError: instance.validate is not a function
How do I make the above function execute?
What you're exporting and what you're doing with the import don't match. The problem is (probably) the export. What you have is this:
module.exports = (instance) => {
instance.validate = async (ctx) => {
// some code here
}
}
That exports a function that, when called, will add a validate method to the object that you pass to it. It does not export an object with a validate method, that would look like this:
module.exports = {
validate: async (ctx) => {
// some code here
},
};
So you need to either fix the export (which I suspect is the problem), or (if the export is really meant to do that), test what it actually does by passing in an object and then checking that, after the call, the object has a validate method.
I'm having a bit of trouble unmocking a function.
I first mock it and now I can't unmock it
//myClass.js
class myClass {
static check(v1,v2) {
return v1 > v2;
}
static async getinfo(v1,v2) {
if (this.check(v1,v2)) {
return await get('api.google.com');
}
return [];
}
}
//myclass.spec.js
describe('Testing myClass', () => {
describe('testing processing', () => {
it('should return result', () => {
const mockPatch = jest.fn().mockImplementation((version, solution) => false);
myClass.check = mockCheck;
try {
const result = await myClass.getinfo(1,2);
expect(result).toBe.([]);
}catch(e) {
throw e;
}
})
})
describe('Testing check', () => {
it('should return true', () => {
expect(myClass.check(2,1)).toBe.true
})
})
})
I already try with
myClass.check.mockRestore()
beforeEach(() => {myClass.check.mockRestore()})
jest.unmock('./myClass.js)
Is there anyway I can solve this? I read all the jest doc and i couldn't find anything
Methods should never be mocked by reassigning them, there is no way how Jest could restore their original implementation this way.
This should always be done with spyOn:
jest.spyOn(myClass, 'check').mockReturnValue(false)
This way a method can be restored with restoreMock or restoreAllMocks. This should be preferably enabled globally in Jest configuration.
I'm assuming that what you're hoping to do is to mock an implementation for use in a specific test, but then have your other tests function without the mocking.
If so, I think you could use the module mocking strategy in conjunction with mockReturnValueOnce.
Be sure to import your module at the top of your tests, then to call jest.mock with the same path. After that, you should be able to call myClass.check.mockReturnValueOnce, and it will be mocked until the next time it is called. After that, it will function normally 👍
This one is starting to get under my skin...
I'm playing around with Firebase and Functions right now and made a very simple API with express as the middleware.
So I have this route:
...
app.get('/getAuthUrl', async (req, res) => {
const s: sessionManagement.ISessionManagement =
sessionManagement.SessionFactory.createSession(
functions.config().aproxy.session.mode, req, db)
// ...this work
const getback = req.query.getback;
await db.collection('tokens').doc('getback').set({getback});
// this not?!
s.setReturnURL(req.query.getback);
res.setHeader('Cache-Control', 'private');
res.status(200).json(new Message(redirectUri));
})
At first, I use the 2 lines under the comment "...this work" and it did work; writing an URL to a Firestore database. So far, so good.
Then I decided to get fancy and decide to use a object factory to manage the fact that using Express session on a localhost serving functions (with the emulator) on one port and the angular frontend on an other was causing some headache with session management. Long story short, I setup a factory that will return me a object that will manage if I was running localy or on Firebase cloud hosting and used a different strategy.
This is where Promise started to not holding their... promise!?
The line s.setReturnURL(.. call this method inside my factory:
export interface ISessionManagement {
setReturnURL: (value: string) => void
}
export class FirestoreSession implements ISessionManagement {
private database: FirebaseFirestore.Firestore
private fingerprint: string
constructor(database: FirebaseFirestore.Firestore, fingerprint: string) {
this.database = database
this.fingerprint = fingerprint
}
setReturnURL(value: string) {
console.log('setReturnURL')
this.writeDoc(value, 'getback')
}
writeDoc(value: string, document: string) {
(async () => {
console.log('inside async')
const doc = this.database.collection('tokens').doc(document).set({value})
const result = await doc
console.log(result)
})().catch(error => {
console.log(error)
})
console.log('finish writeDoc')
}
}
export class SessionFactory {
public static createSession(mode: string, req: express.Request, db: FirebaseFirestore.Firestore) : ISessionManagement {
if (mode === 'fingerprint' ) {
console.log('fingerprint mode');
// Use for local testing, since in multiport solution, cookie will not be unique to a session
return new FirestoreSession(db, fingerPrintMe(req));
} else {
// For production
return new ExpressSession(req);
}
}
}
So, here the console output of the flow of execution if a use my factory object:
✔ functions[app]: http function initialized (http://localhost:5000/myplayground/us-central1/app).
i functions: Beginning execution of "app"
> fingerprint mode
> setReturnURL
> inside async
> finish writeDoc
i functions: Finished "app" in ~1s
The code above is my latest attempt to make this work. I tried A LOT of variation, moving my async/await around but nothing budge!
What am I missing??? I feel the answer will make me crawl under some rock but I don't care, I need to take this one off my mind :-)
UPDATE
Turns out that I need to async/await all the way down to the call that return a promise to make this work AND ALSO, you cannot await a function that return void, so I had to change the interface signature. So here the code fragment that work :
app.get('/getAuthUrl', async (req, res) => {
...
await s.setReturnURL(req.query.getback)
...
})
...
export interface ISessionManagement {
setReturnURL: (value: string) => any
}
export class FirestoreSession implements ISessionManagement {
...
async setReturnURL(value: string) {
await this.writeDoc(value, 'getback')
}
...
async writeDoc(value: string, document: string) {
try {
const doc = await this.database.collection('tokens').doc(document).set({value})
console.log(doc)
} catch (error) {
console.log(error)
}
}
}
const doc = this.database.collection('tokens').doc(document).set({value})
const result = await doc
Why not just const result = await this.database.collection('tokens').doc(document).set({value})?
`writeDoc(value: string, document: string) {
(async () => {
console.log('inside async')
const doc = this.database.collection('tokens').doc(document).set({value})
const result = await doc
console.log(result)
})().catch(error => {
console.log(error)
})
console.log('finish writeDoc')
}`
First, writedoc is not a promise and isn't awaited. The internal async function isn't awaited either, so once execution pauses the rest of the function moves on. So it will execute and the rest of internal async function will be queued for the next event loop. Then everything returns and exits.
Make writedoc async, and await it and the internal async function.
// Balance.jsx
...
updateToken () {
const parseResponse = (response) => {
if (response.ok) {
return response.json()
} else {
throw new Error('Could not retrieve access token.')
}
}
const update = (data) => {
if (data.token) {
this.data.accessTokenData = data
} else {
throw new Error('Invalid response from token api')
}
}
if (this.props.balanceEndpoint !== null) {
return fetch(this.props.accessTokenEndpoint, {
method: 'get',
credentials: 'include'
})
.then(parseResponse)
.then(update)
.catch((err) => Promise.reject(err))
}
}
componentDidMount () {
this.updateToken()
.then(() => this.updateBalance())
}
}
// Test
it('updates the balance', () => {
subject = mount(<Balance {...props} />)
expect(fetchMock.called('balance.json')).to.be.true
})
I can't figure out how to test the above using Mocha. The code is does work the method updateBalance is called and the fetch api call actually does happen, but the test still fails. If I call updateBalance() synchronously it passes... How do I tell the test to wait for the promise to resolve?
You don't really say what you want to test that the
method does, but if all you want to test is that the method resolves on a network call, then there is no need for Sinon or any of that, as this is all you need:
describe("BalanceComponent", () => {
it("should resolve the promise on a successful network call", () => {
const component = new BalanceComponent({any: 'props', foo: 'bar'});
// assumes you call a network service that returns a
// successful response of course ...
return component.updateToken();
});
});
This will test that the method actually works, but it is slow and is not a true unit test, as it relies on the network being there and that you run the tests in a browser that can supply you with a working implementation of fetch. It will fail as soon as you run it in Node or if the service is down.
If you want to test that the method actually does something specific, then you would need to to that in a function passed to then in your test:
it("should change the token on a successful network call", () => {
const component = new BalanceComponent({any: 'props', foo: 'bar'});
const oldToken = component.data.accessTokenData;
return component.updateToken().then( ()=> {
assert(oldToken !== component.data.accessTokenData);
});
});
If you want to learn how to test code like this without being reliant on there being a functioning link to the networked service you are calling, you can check out the three different techniques described in this answer.