I'm using Jest to test a file written in react, I'm trying to mock the hooks but for some reason I still fail. I've tried to follow several suggestions (included stackoverflow answers) but nothing is working. I've tried to simplify the code to find my mistake but it still failing.
// myFileToTest.js
import { useContext, useState } from 'react';
export const returnsUseContext = () => {
return useContext;
};
export const returnsUseState = () => {
return useState;
};
// myFileToTest.test.js
import React from 'react';
import { returnsUseContext, returnsUseState } from './myFileToTest';
describe('test 1', () => {
let realUseContext;
let useContextMock;
beforeEach(() => {
realUseContext = React.useContext;
useContextMock = React.useContext = jest.fn();
});
// Cleanup mock
afterEach(() => {
React.useContext = realUseContext;
});
it('first try', () => {
expect(returnsUseContext()).toBe(useContextMock);
});
});
describe('test 2', () => {
beforeEach(() => {
jest.spyOn(React, 'useState').mockReturnValue({
name: 'hello'
});
});
it('second try', () => {
const useStateMock = jest.spyOn(React, 'useState');
expect(returnsUseState()).toBe(useStateMock);
});
});
and are both failing. Am I making any silly mistakes?
Related
I have the following test which works.
import React from 'react';
import { mount } from 'enzyme';
import MyLogin from '../../src/components/MyLogin';
const mockContent = {
data: {
config: {
myImage: 'mock_image',
},
},
};
jest.mock('../../src/utils/config', () => ({
Consumer: ({ children }) => children(mockContent),
}));
describe('Component Test', () => {
beforeEach(() => {
jest.resetModules();
});
const render = () => mount(
<MyLogin />
);
it('should render the component', () => {
const renderedModule = render();
expect(renderedModule).toMatchSnapshot();
});
});
But I wish to add another test where the values inside the mockContent changes.
I have added a isValid key.
Thus tried the following where I am trying to over ride mock only for this test.
But this doesn't work. No errors. No effect from this mock.
If I had added this new key isValid at the above mock outside the test, it does have the desired effect. (Can't do that of course cos that would affect my other tests)
Could I get some help with this pls.
describe('Component Test', () => {
// other tests
it('my new test', () => {
// attempting to override the above mock.
const myNewMockContent = {
data: {
config: {
myImage: 'mock_image_2',
isValid: true,
},
},
};
jest.mock('../../src/utils/config', () => ({
Consumer: ({ children }) => children(myNewMockContent),
}));
const renderedModule = render();
expect(renderedModule).toMatchSnapshot();
});
});
I am new to Jest and Enzyme & facing an issue. Can someone help?
Here is my jsx file (myTemplate.jsx) :
export default (props) => {
const oneSection = () => {
return <div>Hello</div>
};
return (
props.hasData ? { <div>Hey</div> } : { <div><oneSection/></div> }
)
}
And this is my test file :
import React from "react";
import { shallow } from "enzyme";
import myTemplate from '../myTemplate';
describe("template component", () => {
const props= {
hasData: false,
}
let myComponent = null;
beforeAll(() => {
myComponent = shallow(<myTemplate {...props}/>);
})
test("should render with initial state properly", () => {
expect(myComponent).toMatchSnapshot();
})
})
Now this test case is running successfully. Snapshot is getting created with a div which has oneSection but oneSection is not getting replaced with actual html within it. Basically these lines are not getting covered :
const oneSection = () => {
return <div>Hello</div>
};
How can i cover this piece of code using Jest and enzyme ?
import React from 'react'
import { cleanup, render } from '#testing-library/react'
describe('DMReports', () => {
afterEach(cleanup)
test('DMReports: hasData true', () => {
const { container } = render(
<DMReports hasData={true}/>,
)
expect(container).toMatchSnapshot()
})
test('DMReports: hasData false', () => {
const { container, getByText } = render(
<DMReports hasData={false}/>,
)
expect(container).toMatchSnapshot()
expect(getByText('Hello')).toBeTruthy()
})
})
I'm wondering why I need to put fetch mock logic inside my test to make it work.
Here is simple example:
Component to test with fetch inside useEffect and state update after response:
// Test.jsx
import React, {useEffect, useState} from 'react'
export const Test = () => {
const [description, setDescription] = useState<string | null>(null)
const fetchData = async () => {
const response = await fetch('https://dummyendpoint/');
const parsed = await response.json();
const description = parsed.value;
setDescription(description);
}
useEffect(() => {
fetchData();
}, [])
return (
<div data-testid="description">
{description}
</div>
)
};
export default Test;
Test logic:
// Test.test.js
import React from 'react';
import {render, screen} from '#testing-library/react';
import Test from "./Test";
global.fetch = jest.fn(() => Promise.resolve({
json: () => Promise.resolve({
value: "Testing something!"
})
}));
describe("Test", () => {
it('Should have proper description after data fetch', async () => {
// need to put mock logic here to make it work
render(<Test/>);
const description = await screen.findByTestId('description');
expect(description.textContent).toBe("Testing something!");
});
})
If I keep global.fetch mock at the top of my test file, I keep getting an error:
TypeError: Cannot read property 'json' of undefined
at const parsed = await response.json();
It's really strange that it does not work as it is.
But I was able to fix it by moving the setup into beforeEach block (I assume beforeAll would also work).
It is a common pattern to backup global variable value, override it for tests and restore it back.
import React from 'react';
import { render, screen } from '#testing-library/react';
import Test from "./Test";
describe("Test", () => {
let originalFetch;
beforeEach(() => {
originalFetch = global.fetch;
global.fetch = jest.fn(() => Promise.resolve({
json: () => Promise.resolve({
value: "Testing something!"
})
}));
});
afterEach(() => {
global.fetch = originalFetch;
});
it('Should have proper description after data fetch', async () => {
// need to put mock logic here to make it work
render(<Test />);
const description = await screen.findByTestId('description');
expect(description.textContent).toBe("Testing something!");
});
});
I am trying to test below function or in other words I am trying to write unit test cases of below function.But I am getting error _axios.default.get.mockResolvedValueOnce is not a function
import React from "react";
import axios from "axios";
export default () => {
const [state, setState] = React.useState([]);
const fetchData = async () => {
const res = await axios.get("https://5os4e.csb.app/data.json");
setState(res.data);
};
React.useEffect(() => {
(async () => {
await fetchData();
})();
}, []);
return [state];
};
here is my code
https://codesandbox.io/s/awesome-jepsen-5os4e?file=/src/usetabData.test.js
I write unit test case like that
import useTabData from "./useTabData";
import { act, renderHook, cleanup } from "#testing-library/react-hooks";
import mockAxios from "axios";
describe("use tab data", () => {
afterEach(cleanup);
it("fetch tab data", async () => {
mockAxios.get.mockResolvedValueOnce({
data: {
name: "hello"
}
});
await act(async () => renderHook(() => useTabData()));
expect(mockAxios.get).toHaveBeenCalled();
});
});
Code sandbox doesn't support manual mocks as far as I know.
However, your __mock__ is placed in wrong directory structure. It should be a sibling of node_module.
Having said that, easiest way is to use https://github.com/ctimmerm/axios-mock-adapter
import useTabData from "./useTabData";
import { act, renderHook, cleanup } from "#testing-library/react-hooks";
import axios from "axios";
import MockAxiosAdapter from "axios-mock-adapter";
const mockAxios = new MockAxiosAdapter(axios);
afterEach(() => {
cleanup();// this is not needed . its done by testing library after each.
mockAxios.reset();
});
describe("use tab data", () => {
it("fetch tab data", async () => {
mockAxios.onGet(200, { data: { test: "123" } }); // response code and object.
const { result, waitForNextUpdate } = renderHook(() => useTabData());
const [value] = result.current;
// assert value
// assert the axios call by using history object
});
});
You can use history to assert: https://github.com/ctimmerm/axios-mock-adapter#history
I have got code for usePrevious hook from somewhere on internet. The code for usePrevious looks like:
export const usePrevious = (value) => {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
};
Now, I am learing testing react with jest and enzyme. So, I tried to test usePrevious and got some problems. Here is my test case:
import React from 'react';
import { render } from 'enzyme';
import { usePrevious } from './customHooks';
const Component = ({ children, value }) => children(usePrevious(value));
const setup = (value) => {
let returnVal = '';
render(
<Component value={value}>
{
(val) => {
returnVal = val;
return null;
}
}
</Component>,
);
return returnVal;
};
describe('usePrevious', () => {
it('returns something', () => {
const test1 = setup('test');
const test2 = setup(test1);
expect(test2).toBe('test');
});
});
When the test execution completes, I get this error:
Expected: 'test', Received: undefined
Can anyone please let me know why am I getting undefined and is this the correct way to test custom hoooks in react?
After suggestion from comments from #Dmitrii G, I have changed my code to re-render the component (Previously I was re-mounting the component).
Here is the updated code:
import React from 'react';
import PropTypes from 'prop-types';
import { shallow } from 'enzyme';
import { usePrevious } from './customHooks';
const Component = ({ value }) => {
const hookResult = usePrevious(value);
return (
<div>
<span>{hookResult}</span>
<span>{value}</span>
</div>
);
};
Component.propTypes = {
value: PropTypes.string,
};
Component.defaultProps = {
value: '',
};
describe('usePrevious', () => {
it('returns something', () => {
const wrapper = shallow(<Component value="test" />);
console.log('>>>>> first time', wrapper.find('div').childAt(1).text());
expect(wrapper.find('div').childAt(0).text()).toBe('');
// Test second render and effect
wrapper.setProps({ value: 'test2' });
console.log('>>>>> second time', wrapper.find('div').childAt(1).text());
expect(wrapper.find('div').childAt(0).text()).toBe('test');
});
});
But still I am getting the same error
Expected: "test", Received: ""
Tests Passes when settimeout is introduced:
import React from 'react';
import PropTypes from 'prop-types';
import { shallow } from 'enzyme';
import { usePrevious } from './customHooks';
const Component = ({ value }) => {
const hookResult = usePrevious(value);
return <span>{hookResult}</span>;
};
Component.propTypes = {
value: PropTypes.string,
};
Component.defaultProps = {
value: '',
};
describe('usePrevious', () => {
it('returns empty string when component is rendered first time', () => {
const wrapper = shallow(<Component value="test" />);
setTimeout(() => {
expect(wrapper.find('span').text()).toBe('');
}, 0);
});
it('returns previous value when component is re-rendered', () => {
const wrapper = shallow(<Component value="test" />);
wrapper.setProps({ value: 'test2' });
setTimeout(() => {
expect(wrapper.find('span').text()).toBe('test');
}, 0);
});
});
I am not a big fan of using settimeout, so I feel that probably i am doing some mistake. If anyone knows a solution that does not use settimeout, feel free to post here. Thank you.
Enzyme by the hood utilizes React's shallow renderer. And it has issue with running effects. Not sure if it's going to be fixed soon.
Workaround with setTimeout was a surprise to me, did not know it works. Unfortunately, it is not universal approach since you'd need to use that on any change that cases re-render. Really fragile.
As a solution you can use mount() instead.
Also you may mimic shallow rendering with mount() with mocking every nested component:
jest.mock("../MySomeComponent.jsx", () =>
(props) => <span {...props}></span>
);