Mocking classes with Jest - class

Given I have a class that calls a function doStuff like so:
const myService = require(`./myService`),
service = new myService();
exports.doStuff = async (callback) => {
try {
const data = await service.doOtherStuff(); //I want to mock this
return callback(null, data);
} catch (err) {
callback(new Error(` > feed failed for ${key}, error: ${err}`));
}
};
my tests work like this:
const myClass = require(`../index`);
jest.mock(`./../myService`, () => {
return function() {
return {
doOtherStuff: () => {
return 1;
}
};
};
});
describe(`service-tests - index`, () => {
test(`doStuff and test otherStuff`, async () => {
const result = await myClass.doStuff((err, data) => {
return data;
});
expect(result).toBe(1);
});
});
And my service:
class myService{
constructor() {
//Do constructor stuff
}
async doOtherStuff() {
//Do other stuff
}
This works but now I just have my mock on this file and not by test.
What I need is to have my mock be changable test by test but cannot seem to figure out how this works together with require.
I tried just doing jest.mock('./../myService') and have the mockImplementation on the beforeAll but that will keep my function in mocked as the automatic mock it seems, returning undefined
Anyone ever done this before?

If you want to mock a method within a class, which it looks like you're doing, I would suggest you use jest.spyOn. It's simple, and you can mock the return value to be whatever you want per test.
const myClass = require('../index');
const myService = require('../myService');
describe('service-tests - index', () => {
let doOtherStuffMock;
beforeAll(() => {
doOtherStuffMock = jest.spyOn(myService.prototype, 'doOtherStuff');
});
it('mocks doOtherStuff one way', async () => {
doOtherStuffMock.mockResolvedValue('I am a mocked value');
const result = await myClass.doStuff((err, data) => data);
expect(result).toBe("I am a mocked value");
});
it('mocks doOtherStuff another way', async () => {
doOtherStuffMock.mockResolvedValue('I am DIFFERENT mocked value');
const result = await myClass.doStuff((err, data) => data);
expect(result).toBe('I am DIFFERENT mocked value');
});
});

Update
I'll leave this answer here since both of these approaches work and they can be useful...
...but for this particular case #Daniel is right, mocking the prototype method is easiest
An easy way to handle this is to mock myService.js as a singleton...
...then you can grab the mock function for doOtherStuff and change it per test:
const myClass = require(`../index`);
jest.mock(`../myService`, () => {
const doOtherStuff = jest.fn(); // <= create a mock function for doOtherStuff
const result = { doOtherStuff };
return function() { return result; }; // <= always return the same object
});
const doOtherStuff = require('./myService')().doOtherStuff; // <= get doOtherStuff
describe(`service-tests - index`, () => {
test(`doStuff and test otherStuff`, async () => {
doOtherStuff.mockReturnValue(1); // <= mock it to return something
const result = await myClass.doStuff((err, data) => data);
expect(result).toBe(1); // Success!
});
test(`doStuff and test otherStuff`, async () => {
doOtherStuff.mockReturnValue('something else'); // <= mock it to return something else
const result = await myClass.doStuff((err, data) => data);
expect(result).toBe('something else'); // Success!
});
});
It also works to auto-mock myService.js and use mockImplementation...
...but since index.js creates a myService instance as soon as it runs you have to make sure the mock is in place before you require index.js:
jest.mock(`../myService`); // <= auto-mock
const myService = require('../myService');
const doOtherStuff = jest.fn();
myService.mockImplementation(function() { return { doOtherStuff }; });
const myClass = require(`../index`); // <= now require index
describe(`service-tests - index`, () => {
test(`doStuff and test otherStuff`, async () => {
doOtherStuff.mockReturnValue(1); // <= mock it to return something
const result = await myClass.doStuff((err, data) => data);
expect(result).toBe(1); // Success!
});
test(`doStuff and test otherStuff`, async () => {
doOtherStuff.mockReturnValue('something else'); // <= mock it to return something else
const result = await myClass.doStuff((err, data) => data);
expect(result).toBe('something else'); // Success!
});
});

Related

Testing tanstack-react-qiery - (wait for is not a function in renderHook)

I want to test this customHook but it's showing me an error of
waitFor is not a function.
import { QueryClient, QueryClientProvider } from "#tanstack/react-query";
import { renderHook } from "#testing-library/react";
import { useCustomHookForTestCase } from "../useQueryCustomHook";
const queryClient = new QueryClient();
// jest.mock('../../../Utilies/Apicall')
describe('Testing custom hooks of react query', () => {
const wrapper = ({ children }) => (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
);
it('Should get data of todos from api',async () => {
const { result, waitFor } = await renderHook(() => useCustomHookForTestCase(), { wrapper });
await waitFor(() => result.current.isSuccess );
expect(result.current.data).toEqual("Hello");
})
})
export function useCustomHookForTestCase() {
return useQuery(['customHook'], () => 'Hello');
}
only renderHook from react-hook-testing-library returns a waitFor method. It is not available if you use the one from #testing-library/react-hooks. For that to work, you just need to import waitFor directly. You also can't return a boolean directly, but it needs to be a promise:
import { renderHook, waitFor } from '#testing-library/react'
await waitFor(() => expect(result.current.isSuccess).toBe(true))

Propagate live updates return promise object instead of value

Working on beginner steps toward vue 3 and firestore migration. Stuck on simple.
import { getUsersCount } from "/src/firebase";
setup() {
const usersCount = getUsersCount();
return {
usersCount,
};
},
Why it returns Promise Object, I cant find in manuals.
export const getUsersCount = async () => {
// const querySnap = await getDocs(query(collection(db, "users")));
const q = query(collection(db, "users"));
const unsub = onSnapshot(q, (querySnapshot) => {
console.log("usersCount33: ", querySnapshot.size);
//unsub();
return querySnapshot.size;
});
}
Nad the last part with template,
<template>
<p>Users Count: {{ usersCount }}</p>
</template>
If you return the value inside a callback, you can not use async await syntax. You should do this:
export const getUsersCount = () => {
return new Promise((resolve, reject) => {
const q = query(collection(db, "users"));
const unsub = onSnapshot(q, (querySnapshot) => {
return resolve(querySnapshot.size)
});
})
}
// You still need to wait getUsersCount when using it
const usersCount = await getUsersCount();

Nexjs + SWR: API resolved without sending a response for /api/projects/<slug>, this may result in stalled requests

Since on first render I was not able to get the router.query I am passing the params from getServerSideProps as follows:
export async function getServerSideProps(context) {
return {
props: { params: context.params },
};
}
Then in the function am trying to do the API call but am getting the API stalled error
API resolved without sending a response for
/api/projects/nichole_robel23, this may result in stalled requests.
This is my code:
export default function Project({ params }) {
const { slug } = params;
let [projectData, setProjectData] = useState([]);
let [loading, setLoading] = useState(true);
const { data } = useSWR('http://localhost:3000/api/projects/' + slug);
useEffect(() => {
if (data) {
setProjectData(data.data.project);
setLoading(false);
}
}, [data]);
......
I have global SWRCofig as follows
<SWRConfig value={{ fetcher: (url) => axios(url).then(r => r.data) }}>
<Layout>
<Component {...pageProps} />
</Layout>
</SWRConfig>
Any way to solve the problem?
You are missing your fetcher–the function that accepts the key of SWR and returns the data, so the API is not being called.
You are also not returning a response correctly from the API–this is most likely a case of not waiting for a promise/async to be fulfilled correctly.
CLIENT
const fetcher = (...args) => fetch(...args).then((res) => res.json());
export default function Home({ params }) {
const { slug } = params;
const [projectData, setProjectData] = useState([]);
const [loading, setLoading] = useState(true);
const { data } = useSWR(`http://localhost:3000/api/projects/${slug}`, fetcher);
useEffect(() => {
if (data) {
setProjectData(data);
setLoading(false);
}
}, [data]);
API
const getData = () => {
return new Promise((resolve, reject) => {
// simulate delay
setTimeout(() => {
return resolve([{ name: 'luke' }, { name: 'darth' }]);
}, 2000);
});
}
export default async (req, res) => {
// below will result in: API resolved without sending a response for /api/projects/vader, this may result in stalled requests
// getData()
// .then((data) => {
// res.status(200).json(data);
// });
// better
const data = await getData();
res.status(200).json(data);
}

How to await a build task in a VS Code extension?

let result = await vscode.commands.executeCommand('workbench.action.tasks.build');
resolves immediately.
How can I await a build task with VS Code API?
I figured it out! Tasks cannot be awaited from vscode.tasks.executeTask, but we can await vscode.tasks.onDidEndTask and check if ended task is our task.
async function executeBuildTask(task: vscode.Task) {
const execution = await vscode.tasks.executeTask(task);
return new Promise<void>(resolve => {
let disposable = vscode.tasks.onDidEndTask(e => {
if (e.execution.task.group === vscode.TaskGroup.Build) {
disposable.dispose();
resolve();
}
});
});
}
async function getBuildTasks() {
return new Promise<vscode.Task[]>(resolve => {
vscode.tasks.fetchTasks().then((tasks) => {
resolve(tasks.filter((task) => task.group === vscode.TaskGroup.Build));
});
});
}
export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(vscode.commands.registerCommand('extension.helloWorld', async () => {
const buildTasks = await getBuildTasks();
await executeBuildTask(buildTasks[0]);
}));
}
Note that currently there is a bug #96643, which prevents us from doing a comparison of vscode.Task objects: if (e.execution.task === execution.task) { ... }
I think this depends on how the main command is executed in the extension.ts
Being new to JS/TS, I may be wrong here, but just trying to help:
make sure the vscode.command.registerCommand is not asyncronous, like below:
context.subscriptions.push(vscode.commands.registerCommand('extension.openSettings', () => {
return vscode.commands.executeCommand("workbench.action.openSettings", "settingsName");
}));
This would be compared to something async, like below:
context.subscriptions.push(vscode.commands.registerCommand('extension.removeHost', async (hostID) => {
const bigipHosts: Array<string> | undefined = vscode.workspace.getConfiguration().get('extension.hosts');
const newHosts = Hosts?.filter( item => item != hostID.label)
await vscode.workspace.getConfiguration().update('f5-fast.hosts', newBigipHosts, vscode.ConfigurationTarget.Global);
hostsTreeProvider.refresh();
}));

Why resolving an async promise with a .map() function doesn't work for GET with parameters?

I am not sure how to express my question correctly.
Basically resolving an async promise with a .map() function works for simple get functions while it doesn't work for get functions with parameter.
Basically, in this case, router.get('/' ... the following works:
import axios from 'axios'
const url = 'http://localhost:3000/api/library/'
class libraryService {
// Get stories
static getStories () {
return new Promise(async (resolve, reject) => {
try {
const res = await axios.get(url)
const data = res.data
resolve(
data.map(story => ({
...story
}))
)
} catch (err) {
reject(err)
}
})
}
export default libraryService
While in this case, router.get('/:story_name' ..., this variation doesn't work:
class readService {
// Get story to read
static getStoryToRead (storyName) {
return new Promise(async (resolve, reject) => {
try {
const res = await axios.get(url + storyName)
const data = res.data
resolve(
data.map(selectedStory => ({
...selectedStory
}))
...
In here I get an error: 'data.map is not a function'.
Changing to data.products.map() will return an error 'Cannot read property 'map' of undefined'.
However resolving data without .map() function will work on all cases:
try {
const res = await axios.get(...)
const data = res.data
resolve(
data
)
...
Why this is happening and is it correct to just use resolve(data)?
You seem to be asking for a single story in the case that doesn't work. So instead of an array of stories, presuambly you're getting just the one story that you asked for. There's no reason to try to use map.
Minimal changes (but keep reading):
// Minimal changes, but keep reading...
static getStoryToRead (storyName) {
return new Promise(async (resolve, reject) => {
try {
const res = await axios.get(url + storyName);
resolve(res.data);
} catch (err) {
reject(err);
}
});
}
But, both of those functions demonstrate the Promise creation antipattern. You already have a promise, work with it. In this case, you'd probably do that by making the functions async:
static async getStories () {
const {data} = await axios.get(url);
return data.map(story => ({ // Why copy the story objects?
...story
}));
}
static async getStoryToRead (storyName) {
const {data} = await axios.get(url + storyName));
return data;
}
Or with non-async functions:
static getStories () {
return axios.get(url)
.then(({data}) => data.map(story => ({...story}))); // Why copy the story objects?
}
static getStoryToRead (storyName) {
return axios.get(url + storyName))
.then(({data}) => data);
}