Reputation: 333
I want to test my component:
const Courses: React.FC = () => {
const { data, error } = useSWR(
'some url...',
fetcher
);
console.log(data, error);
if (error) {
return (
<CoursesContainer>
<Error>Something went wrong.</Error>
</CoursesContainer>
);
}
if (!data) return <Loader title="loader" />;
return (
<CoursesContainer>
<CollapsibleTable courses={data} />
</CoursesContainer>
);
};
export default Courses;
but I don't know why I can't mock it to return different value for each test. I've tried that:
jest.mock('../../utils/fetcher', () => ({
fetcher: jest
.fn()
.mockReturnValue('default')
.mockReturnValueOnce('first call')
.mockReturnValueOnce('second call'),
readData: jest.fn(),
}));
test('Basic render. fetch pending', async () => {
const component = render(<Courses />);
await waitFor(() => component.findByTitle('loader'));
expect(component.baseElement).toMatchSnapshot();
});
test('Basic render, fetch success', async () => {
const component = render(<Courses />);
await waitFor(() => component.findByText('CollapsibleTable'));
expect(component.baseElement).toMatchSnapshot();
});
test('Basic render, fetch error', async () => {
const component = render(<Courses />);
await waitFor(() => component.findByText('Something went wrong.'));
expect(component.baseElement).toMatchSnapshot();
});
and that doesn't work well. For each of tests there is only first call
console.log() - The console.log(data, error);
from Courses.tsx.
The feedback from jest:
console.log
undefined undefined
at Courses (src/components/Courses.tsx:14:11)
console.log
first call undefined
at Courses (src/components/Courses.tsx:14:11)
console.log
first call undefined
at Courses (src/components/Courses.tsx:14:11)
console.log
first call undefined
at Courses (src/components/Courses.tsx:14:11)
And of course the third test (Basic render, fetch error
) is failed cos of that.
I can't use spyOn()
instead, cos of my fetcher
is separate function whithout object.
@@ UPDATE @@
There are my fetcher
and readData
functions:
const fetcher = (url: string) => {
return fetch(url)
.then((response) => response.json())
.then((data: Array<IFetchData>) => readData(data));
};
const readData = (data: Array<IFetchData>) => {
let myData: Array<ICourse> = [];
[ there are some simple operations which create new myData array with
properties which I need (there is not any async operations)]
return myData;
};
Upvotes: 2
Views: 2471
Reputation: 6049
You have to give mock implementation for readData
as well.
According to jest specification,
We can create a mock function with jest.fn(). If no implementation is given, the mock function will return undefined when invoked.
This will make more sense about your test.
await waitForElementToBeRemoved(() => component.getByTitle('loader'));
We're waiting for the loader title to be removed which ensures that the title shows up in the first place and now it is removed when loader is completed.
jest.mock('../../utils/fetcher', () => ({
fetcher: jest
.fn()
.mockResolvedValue('default')
.mockResolvedValueOnce('first call')
.mockResolvedValueOnce('second call'),
readData: jest.fn().mockResolvedValue('Read call'), //provide reseolve value
//jest.fn() returns undefined when we dont't provide implementation
}));
test('Basic render. fetch pending', async () => {
const component = render(<Courses />);
await waitForElementToBeRemoved(() => component.getByTitle('loader'));
expect(component.baseElement).toMatchSnapshot();
});
test('Basic render, fetch success', async () => {
const component = render(<Courses />);
await waitForElementToBeRemoved(() => component.getByText('CollapsibleTable'));
expect(component.baseElement).toMatchSnapshot();
});
test('Basic render, fetch error', async () => {
const component = render(<Courses />);
await waitForElementToBeRemoved(() => component.getByText('Something went wrong.'));
expect(component.baseElement).toMatchSnapshot();
});
@Updated answer
Sorry to say that you can't achieve what you want. The reason is the render function is called only once in your test case so it means that the
fetcher
andreadData
API will call only once.
const mockFn = jest.fn();
jest.mock('../../utils/fetcher', () => ({
fetcher: mockFn.mockResolvedValueOnce('first call'),
readData: mockFn.mockResolvedValue(['Read call']), // returns array
}));
test('Basic render. fetch pending', async () => {
const component = render(<Courses />);
await waitForElementToBeRemoved(() => component.getByTitle('loader'));
expect(mockFn).toHaveBeenCalledTimes(1); // test passed
expect(component.baseElement).toMatchSnapshot();
});
Even your provide mockResolvedValueOnce again it will give undefined
as render function doesn't get a chance to call the second time to mock version of fetcher
and readData
.
Upvotes: 3
Reputation: 21
Looks like your MockReturnValue chain is out of order. The default should be last, like this:
jest.mock('../../utils/fetcher', () => ({
fetcher: jest
.fn()
.mockReturnValueOnce('first call')
.mockReturnValueOnce('second call')
.mockReturnValue('default'),
readData: jest.fn(),
}));
See the approved answer from here: Jest mock the same function twice with different arguments
Upvotes: 2