Fake is not a spy - unit-testing
So i have a method that calls an https request. i've successfully stubbed the calls in other tests but this one is beating me an it just won't work.
in my beforeEach i create the stub
in my afterEach i do stub restore
in the test i create four stub calls.
stub.onCall(0).callsFake((arg1, arg2, arg3, cb, arg5) => {return cb(null, {some object0})
stub.onCall(1).callsFake((arg1, arg2, arg3, cb, arg5) => {return cb(null, {some object1})
stub.onCall(2).callsFake((arg1, arg2, arg3, cb, arg5) => {return cb(null, {some object2})
stub.onCall(3).callsFake((arg1, arg2, arg3, cb, arg5) => {return cb(null, {some object3})
then after my ACT statement i attempt to assert the stubs are called with the correct parameters
sinon.assert.calledWith(stub.firstCall, "blah","blah","blah",, sinon.match.any, "blah")
sinon.assert.calledWith(stub.secondCall, "blah","blah","blah",, sinon.match.any, "blah", "blah")
sinon.assert.calledWith(stub.thirdCall, "blah","blah","blah",, sinon.match.any, "blah")
sinon.assert.calledWith(stub.call(3), "blah","blah","blah",, sinon.match.any, "blah"
Unfortunately my test now reports
AssertError: fake is not a spy
I've console.logs in my code under test and they are reporting my stubs being called.
a call to console.log(stub.callCount) return 1 not 4
if i comment all but the first sinon.assert the test passes.
So my question is what am i doing wrong
I'll post my full setup below
` describe('Update Redis', () => {
const balances = {
"TRTLv2Fyavy8CXG8BPEbNeCHFZ1fuDCYCZ3vW5H5LXN4K2M2MHUpTENip9bbavpHvvPwb4NDkBWrNgURAd5DB38FHXWZyoBh4w": 12000
}
const minPayoutLevel = {
"TRTLv2Fyavy8CXG8BPEbNeCHFZ1fuDCYCZ3vW5H5LXN4K2M2MHUpTENip9bbavpHvvPwb4NDkBWrNgURAd5DB38FHXWZyoBh4w": 10000
}
const endpoint1 = '/transactions/prepare/advanced'
const endpoint2 = '/transactions/send/prepared'
beforeEach(() => {
stub = sinon.stub(apiInterfaces, 'jsonHttpRequest')
})
afterEach(() => {
sinon.restore()
})
it("Should update workers balance, paid ,coin:payments:all and coin.payments.workers:address", () => {
//ARRANGE
let error = null
const endpoint = '/transactions/prepare/advanced'
const endpoint2 = '/transactions/send/prepared'
stub.onCall(0)
.callsFake(function (host, port, data, callback, path) {
return callback(null, {"transactionHash": "396e2a782c9ce9993982c6f93e305b05306d0e5794f57157fbac78581443c55f","fee": 1000,"relayedToNetwork": false})
})
stub.onCall(1)
.callsFake(function (host, port, data, callback, path) {
return callback(null, null)
})
stub.onCall(2)
.callsFake(function (host, port, data, callback, path) {
return callback(null, {"transactionHash": "396e2a782c9ce9993982c6f93e305b05306d0e5794f57157fbac78581443c55f","fee": 1000,"relayedToNetwork": false})
})
stub.onCall(3)
.callsFake(function (host, port, data, callback, path) {
return callback(null, {
"transactionHash": "396e2a782c9ce9993982c6f93e305b05306d0e5794f57157fbac78581443c55f"
})
})
let callback = () => {return}
//ACT
coin.processPayments(balances, minPayoutLevel, callback)
console.log(stub.callCount)
sinon.assert.calledThrice(stub.jsonHttpRequest)
sinon.assert.calledWith(stub.firstCall, config.wallet.host, config.wallet.port, '{"destinations":[{"amount":12000,"address":"TRTLv2Fyavy8CXG8BPEbNeCHFZ1fuDCYCZ3vW5H5LXN4K2M2MHUpTENip9bbavpHvvPwb4NDkBWrNgURAd5DB38FHXWZyoBh4w"}],"feePerByte":2,"unlock_time":0,"mixin":3}', sinon.match.any, endpoint1)
sinon.assert.calledWith(stub.secondCall, config.wallet.host, config.wallet.port, '396e2a782c9ce9993982c6f93e305b05306d0e5794f57157fbac78581443c55f', sinon.match.any, endpoint1, 'DELETE')
sinon.assert.calledWith(stub.thirdCall, config.wallet.host, config.wallet.port, '{"destinations":[{"amount":11000,"address":"TRTLv2Fyavy8CXG8BPEbNeCHFZ1fuDCYCZ3vW5H5LXN4K2M2MHUpTENip9bbavpHvvPwb4NDkBWrNgURAd5DB38FHXWZyoBh4w"}],"feePerByte":2,"unlock_time":0,"mixin":3}', sinon.match.any, endpoint1)
sinon.assert.calledWith(stub.call(3), config.wallet.host, config.wallet.port, '{"transactionHash":"396e2a782c9ce9993982c6f93e305b05306d0e5794f57157fbac78581443c55f"}', sinon.match.any, endpoint2)
})
})
})
`
Related
Spying & mocking return values on ctors & methods in node module with Jest
After much trial/error, searching here on SO, & flexing my Google Fu, throwing in the towel & asking for help. TL/DR - Trying to correctly mock node module, change internal method return types, and spy on ctor's & method calls within the node module. My specific scenario is to test the Microsoft Azure Storage blob SDK #azure/storage-blob, but the questions aren't specific to this package. It's just a good example as 4 LOC's capture achieve a task (upload a file to a storage container) as 2-3 of those LOC's cover 4 scenarios. Here's my code that I want to test, with comments on WHAT exactly I want to test: export async function saveImage(account: string, container: string, imageBuffer: Buffer, imageName: string): Promise<void> { try { // init storage client // (1) want to spy on args passed into ctor const blobServiceClient: BlobServiceClient = new BlobServiceClient(`https://${account}.blob.core.windows.net`, new DefaultAzureCredential()); // init container // (2) same as #1 const containerClient: ContainerClient = await blobServiceClient.getContainerClient(container); // init block blob client // (3) same as #1 & #2 const blockBlobClient: BlockBlobClient = containerClient.getBlockBlobClient(imageName); // save file // (4) same as #1,2, & 3 // (5) manipulate returned value // (6) throw cause method to internally throw error await blockBlobClient.upload(imageBuffer, imageBuffer.length, { blobHTTPHeaders: { blobContentType: 'image/png' } }); return Promise.resolve(); } catch (err: Error) { return Promise.reject(err); } } I've setup a manual mock for the module in the ./__mocks/#azure/storage-blob.ts as follows: const MockStorageBlob = jest.createMockFromModule('#azure/storage-blob'); /** * Utility method throw exception in the `BlockBlobClient.upload()` method. */ (MockStorageBlob as any).__setBlockBlobUpload_toFail = () => { (MockStorageBlob as any).BlobServiceClient = jest.fn().mockImplementation(() => { return { getContainerClient: jest.fn().mockReturnValue({ getBlockBlobClient: jest.fn().mockReturnValue({ upload: jest.fn().mockImplementation(() => { throw new Error('synthetic error'); }) }) }) } }); } module.exports = MockStorageBlob; In my test, I can successfully test for #6 above like this: import { BlockBlobClient, BlockBlobUploadResponse } from '#azure/storage-blob'; import { saveImageToCDN as functionUnderTest } from './saveImageToCDN'; // mock Azure Storage blob NPM package jest.mock('#azure/storage-blob'); describe('check expected with failure', () => { beforeEach((done) => { // reset number of times things have been called jest.clearAllMocks(); done(); }); test(`it calls 'trackException()' when upload throws exception`, async (done) => { expect.assertions(1); // setup test require('#azure/storage-blob').__setBlockBlobUpload_toFail(); // run SUT const imageBuffer = Buffer.from('test string'); functionUnderTest(imageBuffer, 'imageName.png') .then(() => { expect(new Error('should not reach this')).toBeUndefined(); }) .catch((err: Error) => { expect(err).toBeDefined(); }) .finally(() => { done(); }); }); }); ... but I can't figure out the correct syntax to spy on the upload() method (#4), or any of the other things I'm trying to test for (#1-5). If it matters, using Jest v26 on Node v14.
Could the __setBlockBlobUpload_toFail function return references to the mock functions ? That would give something like this : const MockStorageBlob = jest.createMockFromModule('#azure/storage-blob'); /** * Utility method throw exception in the `BlockBlobClient.upload()` method. */ (MockStorageBlob as any).__setBlockBlobUpload_toFail = () => { const upload = jest.fn().mockImplementation(() => { throw new Error('synthetic error'); }); const getBlockBlobClient = jest.fn().mockReturnValue({ upload }); const getContainerClient = jest.fn().mockReturnValue({ getBlockBlobClient }); const BlobServiceClient = jest.fn().mockImplementation(() => { return { getContainerClient } }); (MockStorageBlob as any).BlobServiceClient = BlobServiceClient; return { upload, getBlockBlobClient, getContainerClient, BlobServiceClient }; } module.exports = MockStorageBlob; And in your test you would retrieve them like : // setup test const mockFns = require('#azure/storage-blob').__setBlockBlobUpload_toFail(); // run SUT const imageBuffer = Buffer.from('test string'); functionUnderTest(imageBuffer, 'imageName.png') .then(() => { expect(new Error('should not reach this')).toBeUndefined(); }) .catch((err: Error) => { expect(mockFns.getBlockBlobClient.mock.calls[0][0]).toBe('imageName.png') expect(err).toBeDefined(); }) .finally(() => { done(); });
Proper way to test type-graphql middleware with jest
Context I am trying to write a jest test for an authentication middleware for a resolver function. I am attempting to mock an implementation so that the next function is called so that the test passes. Error The error I receive is "next is not a function". I can verify that the mocked function is called through expect(isAuth).toHaveBeenCalledTimes(1);, but there is clearly an issue with my mocked implementation. Any help is much appreciated. Code //isAuth Middleware import { MiddlewareFn } from "type-graphql"; import { Context } from "../utils/interfaces/context"; export const isAuth: MiddlewareFn<Context> = ({ context }, next) => { const loggedInUserId = context.req.session.id; if (!loggedInUserId) { throw new Error("Not authenticated!"); } return next(); }; //transaction.test.ts jest.mock("../middleware/isAuth", () => { return { isAuth: jest.fn((_, next) => next()), //also tried (next) => next() and (next)=>Promise.resolve(next()) }; }); test("should create a txn successfully", async () => { //ARRANGE const user = await createUser(orm); const txn = createTxnOptions(); const txnToBeCreated = { ...txn, userId: user.id }; //ACT const response = await testClientMutate( TXN_QUERIES_AND_MUTATIONS.CREATE_TXN, { variables: txnToBeCreated, } ); //expect(isAuth).toHaveBeenCalledTimes(1); passes so it's getting called console.log(response); const newlyCreatedTxn: Transaction = (response.data as any) ?.createTransaction; //ASSERT const dbTxn = await em.findOne(Transaction, { id: newlyCreatedTxn.id, }); expect(newlyCreatedTxn.id).toBe(dbTxn?.id); }); //transaction.resolver.ts import { Transaction } from "../entities/Transaction"; import { Arg, Ctx, Mutation, Query, Resolver, UseMiddleware, } from "type-graphql"; import { Context } from "../utils/interfaces/context"; import { isAuth } from "../middleware/isAuth"; #Mutation(() => Transaction) #UseMiddleware(isAuth) async createTransaction( #Arg("title") title: string, #Arg("userId") userId: string, #Ctx() { em }: Context ): Promise<Transaction> { const transaction = em.create(Transaction, { title, user: userId, }); await em.persistAndFlush(transaction); return transaction; }
Replace jest.mock("../middleware/isAuth", () => { return { isAuth: jest.fn((_, next) => next()), //also tried (next) => next() and (next)=>Promise.resolve(next()) }; }); With jest.mock("../middleware/isAuth", () => { return { isAuth: (_, next) => next() }; });
Skip implementation in Jest
Currently I have the following piece of code: function handleConnection(socket: Socket): void { info(`Connection started with socketId: ${socket.id}`) socket.on("joinRoom", (request: string) => handleJoinRoom(socket, request)); socket.on("shareData", (request: IShareDataRequest) => handleShareData(socket, request)); socket.on("disconnect", () => handleDisconnect(socket)); } I want to write a test for each and every events like joinRoom, shareData and disconnect. To isolae the tests I want to only test the second socket.on("shareData", () => ...) call and skip the first socket.on("joinRoom", () => ...) call. I've tried with multiple mockImplementationOnce methods with no success. The test I wrote: it('should emit data to room', () => { const listenMock = listen(); const socketMock = { on: jest.fn() .mockImplementationOnce(() => null) .mockImplementationOnce((event: string, callback: Function) => callback({ roomId: "abcdefg" })), to: jest.fn().mockReturnValue({ emit: jest.fn() }) } jest.spyOn(listenMock, "on").mockImplementationOnce((event: string, callback: Function) => callback(socketMock)) startSocket(); expect(socketMock.to).toHaveBeenCalledWith("abcdefg"); expect(socketMock.to().emit).toHaveBeenCalledWith("receiveData", expect.any(Object)); }) ShareData function: function handleShareData(socket: Socket, request: IShareDataRequest): void { socket.to(request.roomId).emit("receiveData", request); } I would really appreciate if anyone could help me out with this.
You can try the following approach: // define the mockSocket const mockSocket = { // without any implementation on: jest.fn() }; describe("connection handler", () => { // I personally like separating the test setup // in beforeAll blocks beforeAll(() => { handleConnection(mockSocket); }); // you can write assertions that .on // should have been called for each event // with a callback describe.each(["joinRoom", "shareData", "disconnect"])( "for event %s", event => { it("should attach joinRoom handlers", () => { expect(mockSocket.on.mock.calls).toEqual( expect.arrayContaining([[event, expect.any(Function)]]) ); }); } ); describe("joinRoom handler", () => { beforeAll(() => { // jest mock functions keep the calls internally // and you can use them to find the call with // the event that you need and retrieve the callback const [_, joinRoomHandler] = mockSocket.on.mock.calls.find( ([eventName]) => eventName === "joinRoom" ); // and then call it joinRoomHandler("mockRequestString"); }); it("should handle the event properly", () => { // your joinRoom handler assertions would go here }); }); });
Express + Mocha, testing async middleware
I have auth.handler middleware that call some async function that I having problem to test using mocha & chai, using done() not helping either here, example code: middleware async function authHandler(req, res, next) { const token = req.headers.authorization.split(' ')[1] const validCredential = await AuthService.verifyAccess(token) if (!validCredential) { next(new Error('Invalid credential') } next() } test it('should return error', async () => { const token = 'invalid-token' const { req, res} = mockSetup(token) await authHandler(req, res, function next(error) => { expect(res.status).to.be.equal(500) expect(error.message).to.be.equal('invalid credential') }) })
Node.js Express.js: mock request object for unit-test purpose
I have a method that check if request query string has some params: checkMandatoryQueryParams (mandatoryQueryParams: String[], req: Request): void { let result = true; mandatoryQueryParams.forEach((element, idx) => { if (!req.query.hasOwnProperty(element)) { result = false; } }); return result; } in unit-testing i need to mock the request for test it, eg: describe('Utils', () => { it('checkMandatoryQueryParams', () => { const req: Request = new Request(); // pseudo-code req.query = "?foo=test&bar=test"; // pseudo-code expect( checkMandatoryQueryParams(['foo', 'bar'], req) ).toEqual(true); }); }); how can i mock the express request?
Two options: Create your own mock object: describe('Utils', () => { /** * Mocked Express Request object */ let mockedReq; beforeEach(() => { mockedReq = { query: {} }; }) it('checkMandatoryQueryParams', () => { const req = mockedReq; req.query = "?foo=test&bar=test"; // pseudo-code expect(checkMandatoryQueryParams(['foo', 'bar'], req) ).toEqual(true); }); }); Use manual mocks, similar to above: describe('Utils', () => { /** * Mocked Express Request object */ let mockedReq; beforeAll(() => { mockedReq = jest.fn().mockImplementation(() => { const req = { // Implementation }; return req; }); }); beforeEach(() => mockedReq.mockReset()) it('checkMandatoryQueryParams', () => { const req = mockedReq; req.query = "?foo=test&bar=test"; // pseudo-code expect(checkMandatoryQueryParams(['foo', 'bar'], req) ).toEqual(true); }); });