Setting up jest mocks - one way works the other doesn't - jestjs

When setting up jest mocks for a class what does not work for me with an error of "_TextObj.TextObj is not a constructor" is
import { TextObj, } from "#entities/TextObj";
jest.mock('#entities/TextObj', () => {
return jest.fn().mockImplementation((config: TextObjConfig) => {
return { ...
}
});
});
According to https://jestjs.io/docs/es6-class-mocks#calling-jestmock-with-the-module-factory-parameter I had expected the first version to work too - or not?
however what works is
import { TextObj, } from "#entities/TextObj";
jest.mock('#entities/TextObj');
...
beforeAll(() => {
TextObj.mockImplementation((config: TextObjConfig) => {
return {
..
}
});
});

TextObj is a named export and you're trying to mock default export which is why it is throwing the error _TextObj.TextObj is not a constructor.
For mocking named export, you need to do following the changes i.e return an object that contains TestObj property:
import { TextObj, } from "#entities/TextObj";
jest.mock('#entities/TextObj', () => {
TestObj: jest.fn().mockImplementation((config: TextObjConfig) => {
return { ...
}
});
});

Related

How to write the unit test for 'fs unlink' using vitest for the follow function?

deleteDuplicatedImage.ts
import { unlink, PathLike } from "fs";
import { logger } from "../libraries";
export const deleteDuplicatedImage = (imagePath: PathLike) => {
unlink(imagePath, function (error) {
if (error) {
throw error;
}
// if no error is thrown, file has been deleted successfully
logger.info("File was deleted as it already exists in the db!");
});
};
This is the function for which I'm writing test case using vitest framework.
Though, I tried to write the test for it in the following way
deleteDuplicatedImage.spec.ts
require("dotenv").config();
import { nanoid } from "nanoid";
import { afterEach, describe, expect, it, vi } from "vitest";
import * as deleteDuplicatedImage from "../../src/lib/utilities/deleteDuplicatedImage";
const testImagePath: string = `${nanoid()}-testImagePath`;
describe("utilities -> deleteDuplicatedImage", () => {
afterEach(() => {
vi.restoreAllMocks();
});
it("it should throw an error", async () => {
const mockedDeleteDuplicatedImage = vi
.spyOn(deleteDuplicatedImage, "deleteDuplicatedImage")
.mockImplementation((_imagePath: any) => {});
deleteDuplicatedImage.deleteDuplicatedImage(testImagePath);
expect(mockedDeleteDuplicatedImage).toBeCalledWith(testImagePath);
expect(
deleteDuplicatedImage.deleteDuplicatedImage(testImagePath)
).toBeUndefined();
});
});
It is also passed but not including the coverage of the code!!
It should have 100% test coverage

Jest testEnvironment: expect is not defined

I'm using Jest 26.x and I defined a custom testEnvironment as so:
import type { EnvironmentContext } from '#jest/environment';
import { Config } from '#jest/types';
import JSDOMEnvironment from 'jest-environment-jsdom';
declare global {
function expectAndMore<ExpectedObject>(
result: ExpectedObject,
expectedObject: ExpectedObject,
): void;
}
class ApprovalTestEnvironment extends JSDOMEnvironment {
constructor(config: Config.ProjectConfig, context: EnvironmentContext) {
super(config, context);
}
async setup() {
await super.setup();
this.global.expectAndMore = ({ result, expectedObject }) => {
expect(result).toEqual(expectedObject);
};
}
async teardown() {
await super.teardown();
}
getVmContext() {
return super.getVmContext();
}
}
export default ApprovalTestEnvironment;
I then configured a unit test file with these comments at the header of my file so it would use the custom environment defined above:
/**
* #jest-environment ./src/approvals/approvalTestEnvironment.ts
*/
But whenever I'm trying to access the expectAndMoreFunction defined in my custom environment with globalThis.expectObjectForPostRequest(result, expected);, I get the following error:
ReferenceError: expect is not defined
Does anyone what causes this and how to fix it?
Note: I'm also using ts-jest 27.1.4.

When run test, TypeError: Cannot destructure property 'travelDatas' of '(0 , _GetTravelDatas.getTravelDatas)(...)' as it is undefined

When I run test, it show TypeError: Cannot destructure property 'travelDatas' of '(0 , _GetTravelDatas.getTravelDatas)(...)' as it is undefined.
As you see the screenshot: unit test
There isn't any console error or warning.
Could anyone help please
travelListTest.spec.js
import { mount, shallowMount } from '#vue/test-utils'
import TravelList from '../../src/components/TravelList.vue'
import { getTravelDatas } from '../../src/composables/GetTravelDatas'
import ElementPlus from 'element-plus'
const wrapper = shallowMount(TravelList, {
global: {
plugins: [ElementPlus]
}
})
jest.mock('../../src/composables/GetTravelDatas')
describe('TravelList Test', () => {
test('click more will call GoToTravelDetailPage', () => {
wrapper.vm.GoToTravelDetailPage = jest.fn()
console.log(wrapper.html())
wrapper.find('.el-button').trigger('click')
expect(wrapper.vm.GoToTravelDetailPage).toHaveBeenCalled()
})
})
TravelList.vue
.....
<script>
import { ref } from '#vue/reactivity';
import { useRouter } from "vue-router";
import { getTravelDatas } from '../composables/GetTravelDatas'
export default {
name: 'TravelList',
setup() {
const { travelDatas } = getTravelDatas();
const router = useRouter();
function GoToTravelDetailPage(acctractionId) {
router.push({ path: `/travelDetail/${acctractionId}` })
}
return { travelDatas, GoToTravelDetailPage };
},
};
</script>
GetTravelDatas.js
import axios from "axios";
import { ref } from '#vue/runtime-core';
export function getTravelDatas() {
const travelDatas = ref([])
axios.get('https://localhost:5001/MyTravel/GetTravelData')
.then((response) => {
if (!response.data.success) {
alert(response.data.errorMessage)
}else{
travelDatas.value = response.data.travelDetail
}
}).catch((error) => {
alert('Unexpected Error: ', error.message)
console.log(error)
});
return { travelDatas }
}
You are mocking the GetTravelDatas module with an auto-mock version by calling:
jest.mock('../../src/composables/GetTravelDatas')
That means that all the methods exported from that module will be mocked (the real code of the method will not be called) and the mocked version will return undefined when called.
In the code you are testing you then have:
const { travelDatas } = getTravelDatas();
Reading the travelDatas property from undefined is causing the error you are seeing.
You should mock the getTravelDatas method so that it returns the appropriate data. For example, returning an empty array would look like:
getTravelDatas.mockReturnValue([]);

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({});
});
});

Mock.mockImplementation() not working

I have a service class
Service.js
class Service {
}
export default new Service();
And I am trying to provide a mock implementation for this. If I use something like this:
jest.mock('./Service', () => { ... my mock stuff });
It works fine, however I'm not able to access any variables declared outside of the mock, which is a bit limiting as I'd like to reconfigure what the mock returns, etc.
I tried this (inspired by this other StackOverflow article: Service mocked with Jest causes "The module factory of jest.mock() is not allowed to reference any out-of-scope variables" error)
import service from './Service';
jest.mock('./Service', () => jest.fn);
service.mockImplementation(() => {
return { ... mock stuff }
);
Unfortunately when I am trying to run this, I get the below error:
TypeError: _Service2.default.mockImplementation is not a function
I had same problem as #Janos, the other answers didn't help either. You could do two things :
If you need to mock only a function from Service, in your test file:
import service from './Service';
jest.mock('./Service', () => jest.fn());
service.yourFunction = jest.fn(() => { /*your mock*/ })
 
If you need to mock the entire Module:
Say your service.js is in javascript/utils, create a javascript/utils/_mocks_ and inside it create a service.js file, you can then mock the entire class in this file, eg:
const myObj = {foo: "bar"}
const myFunction1 = jest.fn(() => { return Promise.resolve(myObj) })
const myFunction2 = ...
module.exports = {
myFunction1,
myFunction2
}
then in your test file you just add:
jest.mock('./javascript/utils/service')
...functions exported from the mockfile will be then hit through your test file execution.
The mock is equal to jest.fn. You need to call jest.fn to create a mocked function.
So this:
jest.mock('./Service', () => jest.fn);
Should be:
jest.mock('./Service', () => jest.fn());
ran into similar issues and resolved it by using .mockImplementationOnce
jest.mock('./Service', () => jest.fn()
.mockImplementationOnce(() => {
return { ... mock stuff }
})
.mockImplementationOnce(() => {
return { ... mock other stuff }
})
);
now when you run another test it will return the second mock object.
You need to store your mocked component in a variable with a name prefixed by "mock" and make sure you return an object with a default property as you import your Service from the default in your "main.js" file.
// Service.js
class Service {
}
export default new Service();
// main.test.js (main.js contains "import Service from './Service';")
const mockService = () => jest.fn();
jest.mock('./Service', () => {
return {
default: mockService
}
});
I had similar problem, and the cause was that ".spec.js" file had an
import jest from "jest-mock";
After removing this line, it worked.
My mistake was that I was resetting the mock before each test. If you do that, be sure to reconfigure the mock implementation.
For example, change this:
let value;
let onPropertyChange: OnPropertyChangeCallback = jest.fn((changes: any) => {
value = changes["testValue"];
});
const user = userEvent.setup();
beforeEach(() => {
jest.resetAllMocks();
});
to this:
let value;
let onPropertyChange: OnPropertyChangeCallback;
const user = userEvent.setup();
beforeEach(() => {
jest.resetAllMocks();
onPropertyChange = jest.fn((changes: any) => {
value = changes["testValue"];
});
});

Resources