Testing react-hook-form's Controller API using enzyme / Jest - jestjs

Is there a way to test a component that uses react-hook-form's Controller API using Jest/enzyme?
Kindly take a look at this code sandbox to see what my component looks like
I did try mocking Controller using the below for a snapshot test;
jest.mock('react-hook-form', () => ({
Controller: () => (<></>),
useForm: () => ({
control: () => ({}),
handleSubmit: () => jest.fn(),
}),
}))
But then the inputs are not rendered because the mocked Controller would return null (because of the <></>).
Any idea how to go about this, or must I use testing library to have a chance at writing a test for this?

Related

Jest: Mock a nested function which returns key/value pairs multiple times

I am writing a test for an API which calls a nested api multiple times to get a key value pair. The value will always be a boolean and I am trying to mock this service aka KeyValueService in the code below. These and other more booleans are used in the PhotoService and I would like to mock these values so I can change the test to match these values.
I have mocked the booleans and also tried setting mockResolveValuetwice to true twice thinking that it may apply true for both variables valueA and valueB, but it did not work. I will be using this nested service multiple times and not just twice. So far none of the solutions worked. How can I get a desired value for each key value pair? TIA!
jest.mock('../../service/keyValue.service', () => ({
valueA: false,
valueB: false
}));
describe('PhotosService', () => {
let service: PhotosService;
let keyValueService: KeyValueService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [PhotosService],
}).compile();
service = module.get<PhotosService>(PhotosService);
keyValueService.get.mockResolveValue(() => true);
});
it('should be defined', () => {
expect(service).toBeDefined();
valueA.mockResolveValue(() => true);
});
});
But the values doesnt change. I also tried the following,
it('should be defined', () => {
keyValueService.get.mockResolveValue(true);
keyValueService.get.mockResolveValue(true);
expect(service).toBeDefined();
valueA.mockResolveValue(() => true);
});
Alright one thing that worked for me was to set keyValueService.get again in my test or it block to jest.fn() again which worked for me to resolve this issue.
keyValueService.get = jest.fn()...;
Upto to use what you want. Either mockImplementation if need be or mockReturnValue etc. My guess is that it just reassigns the get function to a new mock value for that particular it/test block.

Variable value doesn't change in request intercept

I am trying to intercept some requests in cypress following the click of a button, and I am trying to check if requests were or not made to a list of urls. I used a variable ( isRequestCalled ) as you can see, to update it if the requests were made, but the updates don't seem to be reach the test. When a request is intercepted, the value of isRequestCalled is updated in that scope but it doesn't seem to be visible in the scope of the test. Has anyone encoutered this type of issue, I appreciate all suggestions. Thanks!
describe('E2E test', () => {
let isRequestCalled
beforeEach(() => {
isRequestCalled=false
cy.origin(Cypress.env('ORIGIN_URL'), () => {
localStorage.clear();
})
cy.wait(1000);
cy.visit(Cypress.env('BASE_URL'));
cy.intercept('*', (req) => {
isRequestCalled=Cypress.env("REQUEST_URLS").some((url)=>{
return req.url.includes(url)
}) || isRequestCalled
// cy.wrap(isRequestCalled).as('isRequestCalled')
if (isRequestCalled) {
req.alias = 'apiRequests'
}
}
)
})
it('Consent test: Deny', () => {
cy.get(`[data-testid='${Cypress.env('BANNER_TEST_ID')}']`).should('be.visible').log("Banner visible");
cy.get(`[data-testid='${Cypress.env('BUTTON1_TESTID')}']`).should('be.visible').log("Button 1 visible");
cy.get(`[data-testid='${Cypress.env('BUTTON2_TESTID')}']`).should('be.visible').log("Button 2 visible");
cy.get(`[data-testid='${Cypress.env('BUTTON1_TESTID')}']`).click();
cy.reload();
// cy.get('#isRequestCalled').then(isRequestCalled => {
// console.log(isRequestCalled)
// expect(isRequestCalled).to.eq(false)
// })
cy.wrap(isRequestCalled).should('eq',false)
});
it('Consent test: Allow', () => {
cy.get(`[data-testid='${Cypress.env('BANNER_TEST_ID')}']`).should('be.visible').log("Banner visible");
cy.get(`[data-testid='${Cypress.env('BUTTON1_TESTID')}']`).should('be.visible').log("Button 1 visible");
cy.get(`[data-testid='${Cypress.env('BUTTON2_TESTID')}']`).should('be.visible').log("Button 2 visible");
cy.get(`[data-testid='${Cypress.env('BUTTON2_TESTID')}']`).click();
cy.wait('#apiRequests')
});
});
When using cy.intercept() you actually have no need for any variable counters if you want to ensure that the intercepted call has been made. I would recommend aliasing all of the requests you want to intercept and simply waiting (cy.wait()) for them after you perform the action that should trigger the call
describe('E2E test', () => {
beforeEach(() => {
cy.intercept('firstRequestUrl').as('firstCall');
cy.intercept('secondRequestUrl').as('secondCall')
cy.intercept('thirdRequestUrl').as('thirdCall')
cy.origin(Cypress.env('ORIGIN_URL'), () => {
localStorage.clear();
})
cy.wait(1000); // look into removing that as it looks kinda redundant
cy.visit(Cypress.env('BASE_URL'));
})
it('Does smth', () => {
cy.wait('#firstCall')
cy.get('#secondCallTrigger').click()
cy.wait('#secondCall')
})
it('Does smth else', () => {
cy.get('#thirdCallTrigger').click()
cy.wait('#thirdCall')
})
})
P.S.Keep in mind that js/ts variables in Cypress are very tricky to use between contexts, when you declare something in the describe, hooks, or a different it, you can't easily reach it from another it block.
People use env variables or hacks like this for that, although as you can see there is a native functionality for it.
Be sure to check the rest of intercept documentation and aliases documentation for more information.
It could be that the cy.reload() is resetting isRequestCalled.
If any commands such as visit or reload causes the browser to reset, you will notice the browser clearing and it's a sure sign that variables set up globally such as isRequestCalled are also cleared.
I can't really tell the intention of some of the commands, but perhaps you could test the value before the reload.
cy.wrap(isRequestCalled).should('eq',false)
cy.reload()
Also, what is the trigger for requests intercepted by Cypress.env("REQUEST_URLS")?
If it's cy.visit(Cypress.env('BASE_URL')) then the intercept needs to come before the visit.
If it's cy.get(```[data-testid='${Cypress.env("BUTTON1_TESTID")}']```).click() then the ordering is ok.

How to mock useNavigation hook in react-navigation 5.0 for jest test?

i want to mock useNavigation hook used inside my functional component. Any workaround how to mock it using jest?
import React from 'react';
import { useNavigation } from '#react-navigation/native';
import { TouchableOpacity } from 'react-native';
const MyComponent = () => {
const { navigate } = useNavigation();
const navigateToScreen = () => {
navigate('myScreen', { param1: 'data1', param2: 'data2' })
}
return (<TouchableOpacity onPress={navigateToScreen}>
Go To Screen
</TouchableOpacity>)
}
how to test params being passed in navigate function ?
I know I'm late, but I had the issue where I needed to know know when the navigate function was called.
For it, I mocked the function the following way, so the mockedNavigate would be a jest function that i could use later if i needed to test if the function was actually called:
const mockedNavigate = jest.fn();
jest.mock('#react-navigation/native', () => {
const actualNav = jest.requireActual('#react-navigation/native');
return {
...actualNav,
useNavigation: () => ({
navigate: mockedNavigate,
}),
};
});
This allowed me to use this later and I would know if I could navigate properly in my application:
expect(mockedNavigate).toHaveBeenCalledTimes(1);
Hope this helps.
jest.mock("#react-navigation/native", () => {
const actualNav = jest.requireActual("#react-navigation/native")
return {
...actualNav,
useFocusEffect: () => jest.fn(),
useNavigation: () => ({
navigate: jest.fn(),
}),
}
})
Another quite late solution that is a bit cleaner than the previously suggested ones.
const mockedNavigate = jest.fn();
jest.mock('#react-navigation/native', () => (
{ useNavigation: () => ({ navigate: mockedNavigate }) }));
This does not include any other functionality of the navigation object but it will allow you to test in the following way:
expect(mockedNavigate).toHaveBeenCalledTimes(1);
Or with expect.toHaveBeenCalledWith() (here) if more applicable in your specific situation.
This might not be a solution for a mock of useNavigation, but I found this helpful to achieve my own intent (spying), and it looks like it would achieve the OP's intent (expecting on calls)
import { CommonActions } from '#react-navigation/routers';
jest.spyOn(CommonActions, 'navigate');
This seems to be where the actions that surface out of the useNavigation hook are defined, too. I was unsuccessful to create a mock on this, but the spy works excellently.
Caveat
I'm using react-navigation 6.
I am calling render with a NavigationContainer thus I am not mocking anything in react-navigation, hence I can spy. If you don't do that, and you're looking to truly mock navigation, this won't work.
FWIW, I would recommend trying this approach first, as you can then allow much more of your code to be tested properly, as it would get used.
e.g. For my CaptureLocationScreen which is in a Stack inside a modal.
render(
<NavigationContainer>
<RootStack.Navigator>
<RootStack.Group screenOptions={{ presentation: 'modal', }}>
<RootStack.Screen
name="CaptureLocation"
component={CaptureLocationScreen}
options={({ navigation }: RootStackScreenProps<'CaptureLocation'>) => ({
headerTitle: 'Add new location',
headerLeft: () => (<CloseButton onPressed={navigation.goBack} />),
headerRight: () => (<AcceptButton />)
})} />
</RootStack.Group>
</RootStack.Navigator>
</NavigationContainer>);
References
https://reactnavigation.org/docs/navigation-actions/

How to mock a function and expect it to be called?

I am new to react-testing-library and I have been trying to test one function for a long time.
for example, I want to check if when a button is clicked a given function is called and it's throwing errors. so any help would be highly appreciated and if possible share with me any helpful resources.
signin.js
export default class SignIn extends Component {
constructor(props) {
super(props);
this.state = {
};
}
handleClose = (event, reason) => { };
validate = () => { };
change = (e) => { };
onSubmit = (e) => { };
render() {
return (<div>...</div>);
}
}
Full: https://github.com/blaise82/react-testing-library-try/blob/master/src/views/SignIn.js
this is my test
it('should submit form', async () => {
const { getByLabelText, getByText, container, debug } = render(<SignIn />);
const change = jest.fn();
const onSubmit = jest.fn();
const email = getByLabelText('email');
const password = getByLabelText('password');
const submit = getByLabelText('submit');
userEvent.type(email, 'octopusbn#gmail.com');
expect(email.value).toBe('octopusbn#gmail.com');
expect(password.value).toBe('');
expect(change).toHaveBeenCalled();
console.log(password)
await userEvent.click(submit);
expect(onSubmit).toHaveBeenCalled();
});
Full: https://github.com/blaise82/react-testing-library-try/blob/master/src/test/signin.test.js
results
> Expected number of calls: >= 1
> Received number of calls: 0
please let know what I am doing wrong.
Full code on GitHub: https://github.com/blaise82/react-testing-library-try
You can test a function by mocking all that is coming from outside of the component (aka dependencies) like - a prop callback, an external library api etc.
Before starting, let's go through what all functions are in the component.
Going through the component, I can list them as below:
Event handlers on elements [like handleClose, onSubmit, change in the component]
Functions internal to the component which do not interact with the state/functions outside the component [validate]
prop functions/library apis being called [axios.post]
Let's discuss them one by one --
Event handlers &
Functions internal to component not interacting with state/functions outside of the component
==> Event handlers that are attached to elements can safely be expected to get called. You don't need to test them if they are called. Rather, what you should test is the after-effect of them being called. Also the functions like validate
Let's take example of the change function that you are trying to test. This function after being called sets the state and the state gets reflected into the form elements. We can assert values of the form elements with a helper like this.
prop functions/library apis being called [axios.post]
==> These functions can be mocked and tested for the number of calls/parameters they are called with.
https://jestjs.io/docs/en/mock-functions.html#mocking-modules
In addition to the snippet of mocking jest as given in the link above, in your case -
axios.post.toHaveBeenCalledWith(expectedParms);
Also you can make it return results/errors you want and test respective component behaviour.
Hope you find this helpful. Cheers!
I think this is because you are not actually passing in your mocked functions to the component. You're just instantiating two constants that happen to have the name of the functions you're trying to watch, but are not actually being used anywhere in your component.
It sounds like you want to spy on your component's internal functions to see that they've been called.
Here's something of an example (not tested) based on a post (linked below) that might help you.
describe('spying on "onSubmit" method', () => {
it('should call onSubmit when the button is clicked', () => {
const wrapper = shallow(<SignIn />);
const instance = wrapper.instance();
jest.spyOn(instance, 'onSubmit');
wrapper.find('button').simulate('click');
expect(instance.onSubmit).toHaveBeenCalled();
});
});
Post: https://bambielli.com/til/2018-03-04-directly-test-react-component-methods/#spying-on-incrementcounter

Jest: mock require.resolve calls

I've seen this question but no one has answered it as directly as I'd like so here we go again:
How do I mock calls to require.resolve?
So if I have a module like so:
get-module.js
// just a simple example, don't get too caught up
export default getModuleLocation() {
return require.resolve('./my-module');
}
How can I make my require.resolve call return something else? Here's what I've tried:
get-module.test.js
import getModule from './get-module';
let originalRequireResolve;
beforeAll(() => {
originalRequireResolve = require.resolve;
require.resolve = jest.fn();
});
afterAll(() => {
require.resolve = originalRequireResolve;
});
it('gets the module', () => {
// 🔴 this does NOT work
require.resolve.mockReturnValueOnce('mock-module');
expect(getModule()).toBe('mock-module');
});
The above does not work but the example does a good job of communicating what I'm trying to do. It seems like require is some sort of reserved thing I can't mock.
Any ideas? No workarounds please.

Resources