Jest mock static method that uses Promises - reactjs

When writing React app, I have one class that has several static methods, that every executes axios request and return Promise.
My component uses the class to fetch data and render them. Now I'd like to mock the whole class, not one or two methods, because all of them are in use.
Sample ServerManager.ts:
export class ServerManager {
static getUsers(): Promise<User[]> {
return Axios.get(userPath)
.then((response: Promise<AxiosResponse>): User[] => response.data);
}
// lot of similar methods
}
Sample component:
export function SomeComponent() {
const [users, setUsers] = React.useState<User[]>([]);
Promise.all([
ServerManager.getUsers().then(setUsers), //...
]).then();
return <div>
{users.length ? <div>{users.length}</div>}
</div>;
}
I'd like to fully test this component not changing its logic. Best solution would be to create MockServerManager, that imported would replace the original, but doing so makes ServerManager not defined.
EDIT:
I have created such a mock inside the __mocks__ directory:
export class MockServerManager {
static getUsers(): Promise<User[]> {
return Promise.resolve([user]);
}
// ... more methods
}
jest.mock('../../src/ServerManager', () => ({
ServerManager: MockServerManager
}));
Bot now... the promises never ends. The test is waiting for the promise unless it timeout.
Sample test:
describe('SomeComponent', (): void => {
it('renders', async (): Promise<void> => {
// when
const wrapper: ShallowWrapper = shallow(<SomeComponent/>);
await ServerManager.getUsers().then(() => {
// then
expect(wrapper).toMatchSnapshot();
});
});
});

Try importing ServerManager in MockServerManager component because it usually shows this error when we're not importing the component into our base component i.e MockServerManager or it's because you're using props in that component and not declaring the property of props.

Related

Adding functions to lit web components in react with typescript

I have a web component i created in lit, which takes in a function as input prop. but the function is not being triggered from the react component.
import React, { FC } from 'react';
import '#webcomponents/widgets'
declare global {
namespace JSX {
interface IntrinsicElements {
'webcomponents-widgets': WidgetProps
}
}
}
interface WidgetProps extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> {
var1: string,
successCallback: Function,
}
const App = () =>{
const onSuccessCallback = () =>{
console.log("add some logic here");
}
return(<webcomponents-widgets var1="test" successCallBack={onSuccessCallback}></webcomponents-widgets>)
}
How can i trigger the function in react component? I have tried this is vue 3 and is working as expected.
Am i missing something?
As pointed out in this answer, React does not handle function props for web components properly at this time.
While it's possible to use a ref to add the function property imperatively, I would suggest the more idiomatic way of doing things in web components is to not take a function as a prop but rather have the web component dispatch an event on "success" and the consumer to write an event handler.
So the implementation of <webcomponents-widgets>, instead of calling
this.successCallBack();
would instead do
const event = new Event('success', {bubbles: true, composed: true});
this.dispatch(event);
Then, in your React component you can add the event listener.
const App = () => {
const widgetRef = useRef();
const onSuccessCallback = () => {
console.log("add some logic here");
}
useEffect(() => {
widgetRef.current?.addEventListener('success', onSuccessCallback);
return () => {
widgetRef.current?.removeEventListener('success', onSuccessCallback);
}
}, []);
return(<webcomponents-widgets var1="test" ref={widgetRef}></webcomponents-widgets>);
}
The #lit-labs/react package let's you wrap the web component, turning it into a React component so you can do this kind of event handling declaratively.
React does not handle Web Components as well as other frameworks (but it is planned to be improved in the future).
What is happening here is that your successCallBack parameter gets converted to a string. You need to setup a ref on your web component and set successCallBack from a useEffect:
const App = () => {
const widgetRef = useRef();
const onSuccessCallback = () =>{
console.log("add some logic here");
}
useEffect(() => {
if (widgetRef.current) {
widgetRef.current.successCallBack = onSuccessCallback;
}
}, []);
return(<webcomponents-widgets var1="test" ref={widgetRef}></webcomponents-widgets>)
}

Jest to test a class method which has inner function

I'm writing unit test for once of my .ts file. Where I'm facing a problem and unable to find the solution. Hopefully someone can help me to resolve it.
Problem
While writing unit test. I'm unable to test the value for profile. After calling a method called getProfile().
File setup
Profile.ts
import { getProfileAPI} from "./api";
class ProfileDetails implements IProfileDetails {
public profile: string = ''
constructor() {}
getProfile = async () => {
const { data } = await getProfileAPI();
if (data) {
this.profile = data
}
};
}
const profileDetail = new ProfileDetails();
export default profileDetail;
Profile.spec.ts
import Profile from './Profile';
describe('Profile', () => {
it('getProfile', async () => {
Profile.getProfile = jest.fn();
await Profile.getProfile();
expect(Profile.getProfile).toHaveBeenCalled();
});
});
So the challenge I'm facing here is, I can able to mock the getProfile method. But I'm not able to mock the getProfileAPI function which is called inside the getProfile method.
How can I mock a function which is called inside a mocked method (or) is there any other way to resolve this. Kindly help.
Thanks in advance.
Before answering your questions, I may have some comments :
your test is wrong, all it does is calling the method then checking if it is called, of course it will always pass !
you are not really mocking, in fact you're erasing the old method and it may have some impacts on other tests.
your method "getProfile" should be called "getAndSetProfile", or "syncProfile", or something like that, getProfile is confusing for a developer, he will think it only get the profile and returns it.
I don't recommend creating & exporting an instance of ProfileDetails like this, you should take a look on DI (Dependency Injection) with typedi for example.
Do not forget :
A unit test means that any dependency inside your "unit" should be mock, you must only test the logic inside your "unit" (in your case, the getProfile function, or the class itself).
Here, you are invoking a method called "getProfileAPI" from another service that is not mocked, so you are currently testing its logic too.
This test should work :
Profile.spec.ts
jest.mock('./api', () => ({
getProfileAPI: jest.fn(),
}));
import { getProfileAPI } from "./api";
import Profile from './Profile';
describe('Profile', () => {
it('getProfile', async () => {
await Profile.getProfile();
expect(getProfileAPI).toHaveBeenCalled();
});
});
In our example, Profile.profile will be empty, because even if we mocked to getProfileAPI method, we didn't make it return something. You could test both cases :
jest.mock('./api', () => ({
getProfileAPI: jest.fn(),
}));
import { getProfileAPI } from "./api";
import Profile from './Profile';
const mockGetProfileAPI = getProfileAPI as jest.Mock; // Typescript fix for mocks, else mockResolvedValue method will show an error
describe('Profile', () => {
describe('getProfile', () => {
describe('with data', () => {
const profile = 'TEST_PROFILE';
beforeEach(() => {
mockGetProfileAPI.mockResolvedValue({
data: profile,
});
});
it('should call getProfileAPI method', async () => {
await Profile.getProfile();
expect(mockGetProfileAPI).toHaveBeenCalled(); // Please note that "expect(getProfileAPI).toHaveBeenCalled();" would work
});
it('should set profile', async () => {
await Profile.getProfile();
expect(Profile.profile).toBe(profile);
});
});
describe.skip('with no data', () => {
it('should not set profile', async () => {
await Profile.getProfile();
expect(Profile.profile).toStrictEqual(''); // the default value
});
});
});
});
NB : I skipped the last test because it won't work in your case. Profile isn't recreated between tests, and as it is an object, it keeps the value of Profile.profile (btw, this is a bit weird) between each tests. This is one of the reasons why you should not export a new instance of the class.

Make sure function from React.useContext is called in Jest

Given a custom hook that looks like this:
const getSavedInfo (id) => {
const endpoint = getEndpoint(id)
const {updateInfo} = React.useContext(infoContext)
axios.get(endpoint).then((res) => {
if (res) {
updateInfo(res.data)
}
})
}
How would I go about properly mocking the updateInfo method so I can make sure it was called within my jest test?
You need to pass the provider in test while rendering the custom hook. You can write your own provider function or use react-hooks-testing-library which does the same thing for you. Like this:
import { renderHook } from 'react-hooks-testing-library';
const wrapper = ({ children }) => (
<InfoContext.Provider value={{updateInfo: jest.fn}}>{children}</InfoContext.Provider>
);
it('should call update info', () => {
const { result } = renderHook(() => getSavedInfo(), { wrapper });
// rest of the test code goes here
});
You can find detailed explanation here

Can I memoize a setter function from a React context (useContext)

I'm trying to use useEffect to fetch data when a component is mounted as follows:
useEffect(() => {
axios.get(myUrl)
.then(response => {
setName(response.name);
setAge(response.age);
}).catch(error => {
console.log(error);
});
}, [myUrl, setName, setAge]);
setName and setAge are coming from a context as follows:
import MyContext from './context';
const MyComponent = props => {
const {
setName,
setAge
} = useContext(MyContext);
This issue is that functions, arrays, and objects all register as "changed" dependencies in a useEffect call so it ends up in an infinite loop where it's fetching that data over and over and over again. Can I memoize a function from a context so it knows to only call that effect once?
Note: The consumer of my context is several levels above this component so I'm guessing there's nothing I can do there.
I think you need to define custom context tied to something like userId.
This will provide you a stable identifier which will change only when it is necessary.
There is no way to memoize a function from a context.
That said, you can use a custom hook for useMount, either by importing it from the react-use library or by simply looking at the source code there and declaring it yourself. The following shows how to define useMount and useEffectOnce and then utilize useMount as a workaround:
const useEffectOnce = (effect) => {
useEffect(effect, []);
};
const useMount = (fn) => {
useEffectOnce(() => {
fn();
});
};
useMount(() => {
axios.get(myUrl)
.then(response => {
setName(response.name);
setAge(response.age);
}).catch(error => {
console.log(error);
});
});
With this approach, you don't need to change anything about your context declaration, context provider, or context consumer.

How to unit test a React component that renders after fetch has finished?

I'm a Jest/React beginner. In jest's it I need to wait until all promises have executed before actually checking.
My code is similar to this:
export class MyComponent extends Component {
constructor(props) {
super(props);
this.state = { /* Some state */ };
}
componentDidMount() {
fetch(some_url)
.then(response => response.json())
.then(json => this.setState(some_state);
}
render() {
// Do some rendering based on the state
}
}
When the component is mounted, render() runs twice: once after the constructor runs, and once after fetch() (in componentDidMount()) finishes and the chained promises finish executing).
My testing code is similar to this:
describe('MyComponent', () => {
fetchMock.get('*', some_response);
it('renders something', () => {
let wrapper = mount(<MyComponent />);
expect(wrapper.find(...)).to.have.something();
};
}
Whatever I return from it, it runs after the first time render() executes but before the second time. If, for example, I return fetchMock.flush().then(() => expect(...)), the returned promise executes before the second call to render() (I believe I can understand why).
How can I wait until the second time render() is called before running expect()?
I'd separate concerns, mainly because is easier to maintain and to test. Instead of declaring the fetch inside the component I'd do it somewhere else, for example in a redux action (if using redux).
Then test individually the fetch and the component, after all this is unit testing.
For async tests you can use the done parameter on the test. For example:
describe('Some tests', () => {
fetchMock.get('*', some_response);
it('should fetch data', (done) => { // <---- Param
fetchSomething({ some: 'Params' })
.then(result => {
expect(result).toBe({ whatever: 'here' });
done(); // <--- When you are done
});
});
})
The you can tests your component by just sending the loaded data in the props.
describe('MyComponent', () => {
it('renders something', () => {
const mockResponse = { some: 'data' };
let wrapper = mount(<MyComponent data={mockResponse}/>);
expect(wrapper.find(...)).to.have.something();
});
});
When it comes to testing you need to keep it simple, if your component is difficult to test, then there's something wrong with your design ;)
I've had some success with this, as it doesn't require wrapping or modifying components. It is however assuming there's only one fetch() in the component, but it can be easily modified if needed.
// testhelper.js
class testhelper
{
static async waitUntil(fnWait) {
return new Promise((resolve, reject) => {
let count = 0;
function check() {
if (++count > 20) {
reject(new TypeError('Timeout waiting for fetch call to begin'));
return;
}
if (fnWait()) resolve();
setTimeout(check, 10);
}
check();
});
}
static async waitForFetch(fetchMock)
{
// Wait until at least one fetch() call has started.
await this.waitUntil(() => fetchMock.called());
// Wait until active fetch calls have completed.
await fetchMock.flush();
}
}
export default testhelper;
Then you can use it just before your assertions:
import testhelper from './testhelper.js';
it('example', async () => {
const wrapper = mount(<MyComponent/>);
// Wait until all fetch() calls have completed
await testhelper.waitForFetch(fetchMock);
expect(wrapper.html()).toMatchSnapshot();
});
I found a way to do what I originally asked. I have no opinion (yet) whether it is good strategy or not (in fact I had to refactor the component immediately afterwards, so this question is no longer relevant to what I'm doing). Anyway, here is the testing code (explanation below):
import React from 'react';
import { mount } from 'enzyme';
import { MyComponent } from 'wherever';
import fetchMock from 'fetch-mock';
let _resolveHoldingPromise = false;
class WrappedMyComponent extends MyComponent {
render() {
const result = super.render();
_resolveHoldingPromise && _resolveHoldingPromise();
_resolveHoldingPromise = false;
return result;
}
static waitUntilRender() {
// Create a promise that can be manually resolved
let _holdingPromise = new Promise(resolve =>
_resolveHoldingPromise = resolve);
// Return a promise that will resolve when the component renders
return Promise.all([_holdingPromise]);
}
}
describe('MyComponent', () => {
fetchMock.get('*', 'some_response');
const onError = () => { throw 'Internal test error'; };
it('renders MyComponent appropriately', done => {
let component = <WrappedMyComponent />;
let wrapper = mount(component);
WrappedMyComponent.waitUntilRender().then(
() => {
expect(wrapper.find('whatever')).toBe('whatever');
done();
},
onError);
});
});
The main idea is that, in the testing code, I subclass the component (if this was Python I'd probably monkey-patch it, which works more or less the same way in this case) so that its render() method sends a signal that it executed. The way to send the signal is by manually resolving a promise. When a promise is created, it creates two functions, resolve and reject, which when called terminate the promise. The way to have code outside the promise resolve the promise is by having the promise store a reference to its resolve function in an external variable.
Thanks to fetch-mock author Rhys Evans who kindly explained the manually-resolve-promise trick to me.

Resources