Offline Jest unit test implementation in firebase functions in Nodejs - node.js

I am trying to implement jest unit testing for firebase functions in my application. I was purely looking at implementing an offline test. I have mocked the request and response objects and am still trying to access some other firebase resources on execution. I am not sure whether I missed any sort of mocking. I intend to not use any firebase-related configuration URLs.
Here is my function and test samples,
randomgenerator.ts
import { https } from "firebase-functions";
export const generateTrickyNumber = https.onRequest(async (req, res) => {
corsHandler(req, res, async () => {
try {
let num =0;
switch (req.body.type) {
case "palindrome":
num= await generatePalindrome(req.body.startnumber,req.body.endnumber);
break;
case "prime":
num= await generatePrime(req.body.startnumber,req.body.endnumber);
break;
default:
break;
}
return res.status(200).send(num);
} catch (err) {
console.log({ err });
return;
}
});
});
randomgenerator.test.ts
import 'jest';
import * as httpMock from 'node-mocks-http';
import * as TestFunctions from 'firebase-functions-test';
const testEnv = TestFunctions();
import { generateTrickyNumber } from "../src/randomgenrator.ts";
describe('generateTrickyNumber ', () => {
test('it returns a successful response with a valid card', async () => {
const req = { method: "POST", body: {type:"palindrome", startnumber:1,endnumber:200} };
const res = {
send: (payload) => {
expect(payload).toBe(153)
},
};
await generateTrickyNumber (req as any, res as any);
});
test('it returns an error if the method is not passed', async () => {
const req = { body: { type: "palindrome" } };
const res = {
send: (payload) => {
expect(payload).toBe('Missing value!')
},
};
await generateTrickyNumber (req as any, res as any);
});
});
Do we need to handle any more mocking for making this successful? Please feel free to suggest.

Related

undefined async handler using sinon.stub()

I know it's something obvious that I'm missing but I'm trying to write unit tests using mocha, chai and sinon for an async handler that's referencing another async service. Here's the code for what I'm dealing with:
authenticateService.js
module.exports = (() => {
...
async authenticate(username, password) {
const isValid = await validCreds(username)
if (!isValid) {
throw Error(`invalid_credentials`)
}
return isValid
}
}
authenticateHandler.js
module.export.handler = {
async (event) => {
const auth = await authenticate(username, password)
if (auth) {
// do things
}
return {statusCodes.OK}
}
}
unit test
describe(`Admin Module`, () => {
beforeEach(() => {
sinon.stub(authenticateService.prototype, "authenticate")
.callsFake(() => Promise.resolve({status:200}))
})
afterEach(() => {
authenticationService.restore()
})
describe(`test auth`, () => {
it(`Receives GET event expects statusCode 200`, async () => {
const event = {
httpMethod: `POST`,
resourcePath: `/admin/authentications`,
body: {
username: `fizz#email.com`,
password: `buzz`,
recaptchaToken: `some_token`
},
}
expect(response.statusCode).to.equal(200)
})
})
I tried setting the following:
setting it in a sandbox
using yields, resolve, return and could not figure out how to stub it correctly
tried mocking the nested authenticateService, mocking the handler and mocking both
I keep getting Error: Trying to stub property 'authenticate' of undefined so I know it must not be instantiating the service correctly. Frankly it seems like a simple thing but I just cannot figure out what I am missing.

How to mock function using node-tap on fastify inject

I want to make 100% coverage on this function with node-tap but I can't mock the error part, it always throw
Cannot find module 'create' Require stack: - /home/mptr8/Code/Projects/me/fastify-example/fastify-postgres/test/integration.js
But I have create function on my query.js file, what do I do wrong here? Why it doesn't invoke the method?
t.mock("../query.js", {
create: () => {
throw new Error();
},
});
I also try this combination, because query.js are dependent on db.js. Now the mock error gone but still I'm not getting the error throw from my fastify.inject.
t.mock("../db.js", {
"../query.js": {
create: () => { throw new Error() },
},
});
app.post("/", async (request, reply) => {
try {
const { body } = request;
const book = create(body.title);
reply.send(book);
} catch (error) {
// this part are not covered
reply.code(500).send({ message: "internal server error" });
}
});
here are my complete code. You can see the full code on this github repository.
// server.js
const fastify = require("fastify");
const {
migration,
create,
} = require("./query");
const db = require("./db");
function build(opts = {}) {
const app = fastify(opts);
migration();
app.post("/", async (request, reply) => {
try {
const { body } = request;
const book = create(body.title);
reply.send(book);
} catch (error) {
reply.code(500).send({ message: "internal server error" });
}
});
app.addHook("onClose", (_instance, done) => {
db.close();
done();
});
}
module.exports = build;
// db.js
const { Pool } = require("pg");
const pool = new Pool({
connectionString:
"postgresql://postgres:postgres#localhost:5432/fastify_postgres?schema=public",
});
module.exports = {
query: (text, params) => pool.query(text, params),
close: () => pool.end(),
};
// query.js
const db = require("./db");
async function migration() {
await db.query(`
CREATE TABLE IF NOT EXISTS books (
id serial PRIMARY KEY,
title varchar (255) NOT NULL
)
`);
}
async function create(title) {
return await db.query("INSERT INTO books (title) VALUES ($1)", [title]);
}
module.exports = { migration, create };
// test.js
const tap = require("tap");
const fastify = require("../server");
tap.test("coba", async (t) => {
const app = await fastify();
t.test("should success create books", async (t) => {
const response = await app.inject({
method: "POST",
url: "/",
payload: {
title: "Hello,World!",
},
});
t.equal(response.statusCode, 200);
});
t.test("should throw error", async (t) => {
const app = await fastify();
// it doesn't throw the error :((
t.mock("../query.js", {
create: () => {
throw new Error();
},
});
const response = await app.inject({
method: "POST",
url: "/",
payload: {
title: "Hello,World!",
},
});
t.equal(response.statusCode, 500);
// call app close on last test child to close app and db properly
app.close();
});
});
You should use the returned value by the t.mock function:
const build = t.mock({
"../server": {
"./query.js": {
create: () => { throw new Error() },
}
}
})
const app = await build({})

How to test api route in nextjs with Jest

I have a function in my api folder that searches an array of services and returns a single service depending on the slug.
The api function is:
import {services} from '../../../public/data/service.js'
export default async function getSingleService (req, res) {
const serviceId = req.query.serviceID
const result = services.filter((each) => each.url === serviceId )
if(result.length > 0) {
return res.status(200).json(result[0])
} else {
res.status(404).json({message: 'Service not found'})
}
}
How do I got about to write a test for this function. I just started with jest, any help would be nice.
Not sure if you're still looking for an answer, but this seems to work for me. It works on the default "hello" route. I found it here
import { createMocks } from 'node-mocks-http';
import handler from '../../../pages/api/hello';
describe('/api/hello', () => {
test('should returna an object with a name', async () => {
const { req, res } = createMocks({
method: 'GET'
});
await handler(req, res);
expect(res._getStatusCode()).toBe(
200
);
expect(
JSON.parse(res._getData())
).toHaveProperty('name');
});
});

Mock a custom event emitter with jest

I want to assert that emit from the EventEmitter class was called with specific parameters by using Jest. I have a separate file where I create an instance of eventEmitter to be used, and on the other class I import it and at some point the event is emitted.
// commonEmitter.ts
const events = require('events');
export const commonEmitter = new events.EventEmitter();
// class.ts
import { commonEmitter } from (..)
export class MyClass {
(...)
method(){
commonEmitter.emit('eventName', { data: true});
}
}
// class.spec.ts
let commonEmitterMock: any
beforeEach(() => {
commonEmitterMock = createMock('emit');
});
it('testMyClass', async () => {
const method = new MyClass().method();
expect(commonEmitterMock).toHaveBeenCalledWith('eventName', { data: true})
}
With this implementation the emit event is never called..
Cant figure out why, any idea?
To test different branches of your http request events without giving up on a over-engineered code you can do the follow.
This is a stub version of the function I intend to test using Jest:
function myRequest(resolve, reject) {
http.request(url, options, (res: IncomingMessage) => {
response.on('data', (chunk) => {
// On data event code
})
response.on('end', () => {
// On end event code
resolve()
})
response.on('error', (err) => {
reject(err)
})
}
}
Firstly, we need to mock the http library an overwrite the request implementation to manually trigger the callback and inject our mocked response object:
...
const mockRes = {
write: jest.fn(),
on: jest.fn(),
end: jest.fn()
}
jest.mock('http', () => ({
request: jest.fn().mockImplementation((url, options, cb) => {
cb(mockRes)
})
})
Then, each of our jest test unit, we manually trigger the callbacks on each of the events we desire to test passing data to each of the specific callbacks:
it('should call request callback and reject for invalid content response', async () => {
const resolve = jest.fn()
const reject = jest.fn()
mockRes.on.mockImplementation((event, cb) => {
if (event === 'end') {
cb()
} else if (event === 'data') {
cb(new Error('invalid_json_string'))
}
})
// #ts-ignore
myRequest(resolve, reject)
// #ts-ignore
expect(mockRes.on).toHaveBeenCalledWith('data', expect.any(Function))
expect(mockRes.on).toHaveBeenCalledWith('end', expect.any(Function))
expect(reject).toHaveBeenCalledWith(expect.any(Error))
})
It is better to inject the dependency to make your class more testable instead of importing it. So your class will look like
export class MyClass {
constructor(commonEmitter) {
this.commonEmitter_ = commonEmitter;
}
method(){
this.commonEmitter_.emit('eventName', { data: true});
}
}
And then your test file could be
let commonEmitterMock: any
beforeEach(() => {
commonEmitterMock = createMock('emit');
});
it('testMyClass', async () => {
const method = new MyClass(commonEmitterMock).method();
expect(commonEmitterMock).toHaveBeenCalledWith('eventName', { data: true})
}

How to properly stub out request-promise in a mocha unit test using sinon?

My unit test is:
describe.only("Validator Service Tests", function () {
let request
before((done) => {
request = sinon.stub()
done()
})
beforeEach(() => {
process.env.API_URL = "http://test"
})
it('Should return with no errors if the file matches the schema', () => {
const updateStatusSpy = sinon.spy(FileLib, 'updateStatus')
request.yields({message: 'ok'})
return ValidatorService.handleMessage({
file: 'test'
})
.then((response) => {
assert()
console.log(response)
sinon.assert.calledOnce(updateStatusSpy)
assert(response, 'f')
})
})
})
The problem is my handleMessage function, which looks like:
exports.handleMessage = (message, done) => {
return stuff()
.then((result) => {
console.log('result', result)
if(result) {
return FileLib.updateStatus(fileId, 'valid')
}
return FileLib.updateStatus(fileId, 'invalid')
})
.then(done)
}
And my updateStatus function:
exports.updateStatus = function(fileId, status) {
console.log(fileId, status)
return request.put({
uri: `${process.env.API_URL}/stuff/${fileId}`,
body: {
status: status
}
})
}
My actual request call is buried so deep in, how can I stub it out when testing?
I'm not sure I completely understand your question, but if you are just trying to stub put, try something like this:
let stub;
beforeEach(() => {
putStub = sinon.stub(request, 'put').resolves('some_val_or_object'); //or yields or callsFake, depending on what you're using
});
it('should call request with put', async () => {
await //call your code
expect(putStub.called).to.be.true;
expect(putStub.calledWith(whatever_you_want_to_check)).to.be.true;
});

Resources