Why can't I mock `crypto.createHash`? - node.js

I'm trying to test a function which makes use of Node's crypto.createHash function. However, when I try to mock it using jest.mock('crypto', () => ({ createHash: … })), the mock doesn't seem to work.
The unit under test uses both Hash#update and Hash#digest, so I'm trying to add those to the createHash mock, but when the unit runs, createHash returns undefined.
src/hash.ts
import { createHash } from 'crypto';
export function hash(input: string | Uint8Array): Uint8Array {
return new Uint8Array(createHash('sha512').update(input).digest().buffer);
}
src/__test__/hash.test.ts
import { createHash } from 'crypto';
import { hash } from '../hash-node.js';
const mockDigest = jest.fn();
jest.mock('crypto', () => ({
createHash: jest.fn().mockReturnValue({
update: jest.fn().mockReturnThis(),
digest: mockDigest,
}),
}));
describe('hash', () => {
it('uses SHA512', () => {
hash('foo');
expect(createHash).toHaveBeenCalledWith(
expect.stringMatching(/sha512/i),
);
});
});
Output
TypeError: Cannot read properties of undefined (reading 'update')
3 |
4 | export function hash(input: string | Uint8Array): Uint8Array {
> 5 | return new Uint8Array(createHash('sha512').update(input).digest().buffer);
| ^
6 | }
7 |
at hash (src/hash.ts:4:25)
at Object.<anonymous> (src/__test__/hash.test.ts:16:5)
As you can see, createHash is clearly returning undefined, as though .mockReturnValue hadn't been called.
What am I doing wrong? How can I get this module mock to work?

I came back to this and was able to figure it out; it's a result of configuring Jest to reset mocks between tests. Once I moved the mockReturnValue call on createHash to inside a beforeEach block, it all started working as expected.
import { type Hash, createHash } from 'crypto';
import { hash } from '../hash.js';
const mockDigest = jest.fn();
jest.mock('crypto', () => ({
createHash: jest.fn(),
}));
describe('hash', () => {
beforeEach(() => {
(createHash as jest.MockedFunction<typeof createHash>).mockReturnValue({
update: jest.fn().mockReturnThis(),
digest: mockDigest,
} as unknown as Hash);
});
it('uses SHA512', () => {
hash('foo');
expect(createHash).toHaveBeenCalledWith(
expect.stringMatching(/sha512/i),
);
});
});

Related

How to mock prisma with jest-mock

I use prisma to interact with my database and i would like to use jest-mock to mock the findMany call. https://jestjs.io/docs/jest-object#jestmockedtitem-t-deep--false
brands.test.ts
import { PrismaService } from "#services/mysql.service";
import { mocked } from "jest-mock";
import faker from "#faker-js/faker";
import { GetBrands } from "./brand";
jest.mock("#services/mysql.service");
/**
* #group unit
*/
describe("Brand", () => {
afterAll(async () => {});
const mockedPrismaService = mocked(PrismaService, true);
it("should get a list of brands", async () => {
const mockedData = [
{
id: faker.datatype.uuid(),
name: faker.datatype.string(),
image: {
source: "some_source",
dtype: "some_dtype",
},
},
];
//#ts-ignore - because of relational data mockedData.image
mockedPrismaService.brand.findMany.mockResolvedValueOnce(mockedData);
const [response, error] = await GetBrands();
console.log(response, error);
});
});
mysql.service.ts
import mysql from "mysql2/promise";
import { Config } from "#config";
import { PrismaClient, Prisma } from "#prisma/client";
export const MySQLEscape = mysql.escape;
export const MySQLPreparedStatement = mysql.format;
export const PrismaService = new PrismaClient({});
export const PrismaHelper = Prisma;
However when i run this test i get the following error.
TypeError: Cannot read properties of undefined (reading 'brand')
Factory Mock
One option is to option use the factory approach when mocking your client.
jest.mock("#services/mysql.service", () => ({
PrismaService: {
brand: {
findMany: jest.fn(() => { })
}
},
}));
Then within your test, you can mock the findMany function to return your test data, then call the function being tested.
const mockedData = [...];
PrismaService.brand.findMany.mockResolvedValueOnce(mockedData);
const result = await GetBrands();
It's a bit cumbersome, but it works.
Note that in my example, I've implemented GetBrands as follows:
import { PrismaService } from "#services/mysql.service"
export const GetBrands = async () => {
const data = await PrismaService.brand.findMany();
return data;
}
Your example
In your example, you're using automatic mocking, and I'm not too familiar with it so I'm not sure how to get it working.
What seems to be happening to cause the error is your PrismaService is undefined when it's imported here:
import { PrismaService } from "#services/mysql.service";
And then calling the mocked function with an undefined parameter returns undefined:
const mockedPrismaService = mocked(undefined, true); // returns undefined
And finally, calling the following is what throws the error:
mockedPrismaService.brand.findMany.mockResolvedValueOnce(mockedData);
// TypeError: Cannot read properties of undefined (reading 'brand')
I would have thought something like this would be what you're after, but this throws an error:
jest.mock("#services/mysql.service", () => ({
PrismaService: mocked(PrismaService, true)
}));
// 6 |
// 7 | jest.mock("#services/mysql.service", () => ({
//> 8 | PrismaService: mocked(PrismaClient, true)
// | ^
// 9 | }));
Check out the docs
Might be worth checking out the Prismas documentation on unit testing, as they suggest a couple of pretty different approaches.

Sinon stub replaces class property for whole test file instead of describe block

I'm trying to replace a static method of a class so that it returns custom value for testing purposes.
I have multiple test (describe) blocks in my file, and my goal is to replace the static method only in one of the describe blocks.
The problem is that the method is replaced for all the tests, even though I have a teardown method that is replacing the static method with the original method.
Some code:
class Cat {
static sound() {
return "Meow";
}
}
module.exports = {
Cat
}
const sinon = require("sinon");
const { Cat } = require("./myClass");
describe("Main tests", () => {
describe("Test Cat", () => {
it("Meows", () => {
expect(Cat.sound()).toBe("Meow")
})
})
describe("Test Dog", () => {
const stub = sinon
.stub(Cat, "sound")
.callsFake(() => "Woof");
afterAll(() => {
stub.restore();
});
it("Barks", () => {
expect(Cat.sound()).toBe("Woof")
})
})
})
Test results - the unreplaced test case is failing:
FAIL ./index.test.js
Main tests
Test Cat
✕ Meows (6ms)
Test Dog
✓ Barks
● Main tests › Test Cat › Meows
expect(received).toBe(expected) // Object.is equality
Expected: "Meow"
Received: "Woof"
7 | describe("Test Cat", () => {
8 | it("Meows", () => {
> 9 | expect(Cat.sound()).toBe("Meow")
| ^
10 | })
11 | })
12 |
Is there a way how to prevent this?
I tried using createSandbox:
const sandbox = sinon.createSandbox()
const stub = sandbox
.stub(Cat, "sound") // etc
but it's the same thing.
Any help would be appreciated.
This task can be done easily with jestjs only (without sinon).
Just use jest.spyOb function to spy sound function, and you can mock the result of this function:
const { Cat } = require('./myClass');
describe('Main tests', () => {
beforeEach(() => {
jest.spyOn(Cat, 'sound');
});
afterEach(() => {
jest.resetAllMocks();
});
describe('Test Cat', () => {
it('Meows', () => {
// Don't mock, just call actual logic
expect(Cat.sound()).toBe('Meow');
});
});
describe('Test Dog', () => {
it('Barks', () => {
Cat.sound.mockReturnValue('Woof'); // mock return value of `sound()`
expect(Cat.sound()).toBe('Woof');
});
});
});

Mock exported class in Typescript Jest

Hi i wrote following code to fetch blobs from Azure Blob Storage.
import { BlobServiceClient, ContainerClient, ServiceFindBlobsByTagsSegmentResponse } from '#azure/storage-blob';
import { GetBlobPageInput, GetBlobPageOutput, PutBlobItemsInput, GetBlobItem } from './interfaces/blob.service.interface';
export const getBlobsPage = async<T>(input: GetBlobPageInput) => {
const blobServiceClient = BlobServiceClient.fromConnectionString(input.blobConnectionString);
const iterator = blobServiceClient
.findBlobsByTags(input.condition)
.byPage({ maxPageSize: input.pageSize });
return getNextPage<T>({
iterator,
blobServiceClient,
blobContainer: input.blobContainer,
});
};
[...]
I am trying to write a unit test for it, but i have trouble when i try to mock BlobServiceClient from #azure/storage-blob. I wrote sample test and mock as this:
import { getBlobsPage } from './../../services/blob.service';
const fromConnectionStringMock = jest.fn();
jest.mock('#azure/storage-blob', () => ({
BlobServiceClient: jest.fn().mockImplementation(() => ({
fromConnectionString: fromConnectionStringMock,
})),
}));
describe('BLOB service tests', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should fetch first page and return function to get next', async () => {
const input = {
blobConnectionString: 'testConnectionString',
blobContainer: 'testContainer',
condition: "ATTRIBUTE = 'test'",
pageSize: 1,
};
const result = await getBlobsPage(input);
expect(fromConnectionStringMock).toHaveBeenCalledTimes(1);
});
});
But when i try to run test i am getting:
TypeError: storage_blob_1.BlobServiceClient.fromConnectionString is not a function
24 |
25 | export const getBlobsPage = async<T>(input: GetBlobPageInput) => {
> 26 | const blobServiceClient = BlobServiceClient.fromConnectionString(input.blobConnectionString);
| ^
27 |
28 | const iterator = blobServiceClient
29 | .findBlobsByTags(input.condition)
at nhsdIntegration/services/blob.service.ts:26:47
at nhsdIntegration/services/blob.service.ts:1854:40
at Object.<anonymous>.__awaiter (nhsdIntegration/services/blob.service.ts:1797:10)
at Object.getBlobsPage (nhsdIntegration/services/blob.service.ts:25:65)
at tests/services/blob.service.test.ts:27:26
at tests/services/blob.service.test.ts:8:71
Any tips on how should I properly implement mock for azure module?
I've tried following several diffrent answers on StackOverflow and looked through articles on web (like: https://dev.to/codedivoire/how-to-mock-an-imported-typescript-class-with-jest-2g7j). And most of them show that this is the proper solution, so i guess i am missing some small thing here but can't figure it out.
The exported BlobServiceClient is supposed to be a literal object but you're now mocking as function which is the issue.
So you might need to simply mock returning a literal object. Another issue is to access a var fromConnectionStringMock from outside of the mock scope would end up with another issue.
So here's possibly the right mock:
jest.mock('#azure/storage-blob', () => ({
...jest.requireActual('#azure/storage-blob'), // keep other props as they are
BlobServiceClient: {
fromConnectionString: jest.fn().mockReturnValue({
findBlobsByTags: jest.fn().mockReturnValue({
byPage: jest.fn(),
}),
}),
},
}));

Jest mock values returned by function

I have a logger file as below which implements logging functionality. uuidLogger.js
const winston = require('winston'),
CustomTransport = require('./customTransport');
function getLogger(route) {
return winston.createLogger({
defaultMeta: { route },
transports: [new CustomTransport()]
});
}
module.exports = getLogger;
It is imported by a function like this and used for logging testfn.js
const uuidLogger = require('./uuidLogger')('test-fn');
function testMock() {
uuidLogger.info('Hey I am just logging');
}
module.exports = { testMock };
I am trying to mock uuidlogger in testfn.js so that I can track various methods called on uuidLogger object. I tried below approach.
import { testMock } from './testfn';
import getLogger from './uuidLogger';
const logger = getLogger('testfn');
jest.mock('./uuidLogger', () =>
jest.fn(() => ({
info: jest.fn(() => console.log('Mocked function actually called'))
}))
);
it('verify that info method was called on returned object', () => {
testMock('abx');
expect(logger.info).toBeCalledTimes(1);
});
It was able to mock the method called however mock information is not getting reflected in logger.info object.
I also tried below approach
import { testMock } from './testfn';
import getLogger from './uuidLogger';
jest.mock('./uuidLogger', () =>
jest.fn(() => ({ info: jest.fn(() => console.log('Top level fn')) }))
);
const logger = {
error: jest.fn(),
info: jest.fn(() => {
console.log('Overwritten fn');
})
};
getLogger.mockReturnValue(logger);
it('shud return Winston instance', () => {
testMock('abx');
expect(logger.info).toBeCalledTimes(1);
});
Any help on how to get it will be appreciated. Thanks in advance.
It seems to be the assertion is not done on proper variable.
Need to assert on getLogger
Your first approach of writing test case is proper.
Add assertion something like this:
expect(getLogger.mock.results[0].value.info).toBeCalledTimes(1);

How to hook with useEffect/setState

I'm having trouble making the following test pass:
import { useEffect, useState } from "react";
export function useComponentResources(required) {
const [componentResources, setComponentResources] = useState(null);
useEffect(() => {
if (required) {
// api call
setTimeout(() => setComponentResources({}), 100);
}
}, [required]);
return componentResources;
}
import { renderHook } from "#testing-library/react-hooks";
import { useComponentResources } from "./component-resources.hook";
describe("component-resources.hook", () => {
it("fetches resources when required", () => {
//act
const { result } = renderHook(() => useComponentResources(true));
//assert
expect(result.current).toEqual({});
});
});
It keeps failing:
expect(received).toEqual(expected)
Expected value to equal:
{}
Received:
null
Difference:
Comparing two different types of values. Expected object but received null.
7 | const { result } = renderHook(() => useComponentResources(true));
9 | //assert
> 10 | expect(result.current).toEqual({});
11 | });
12 | });
I have created a repro case in codesandbox:
https://codesandbox.io/embed/priceless-browser-94ec2
renderHook doesn't wait for your setTimeout to fire; it can't know what 'side effects' your component has. So when your expect() runs, the current value is still its default - null.
We can force the test to wait until the hook updates again by using waitForNextUpdate, which is on the object renderHook returns. waitForNextUpdate is a function that returns a promise that resolves once the hook is updated again (e.g. when your setTimeout fires).
import { renderHook } from "#testing-library/react-hooks";
import { useComponentResources } from "./component-resources.hook";
describe("component-resources.hook", () => {
it("fetches resources when required", async () => {
const { result, waitForNextUpdate } = renderHook(() => useComponentResources(true));
await waitForNextUpdate();
expect(result.current).toEqual({});
});
});

Resources