Override a mocked consumer body for certain tests - javascript

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();
});
});

Related

Jest mock - how to spy on a function, within a function, within a hook within an ES module?

I have this module that I want to mock:
import { createContext, useContext } from 'react'
const SomeContext = createContext({
set: () => null,
reset: () => null,
})
export const useSomeContext = () => useContext(SomeContext)
... and here is how it is used in a custom react hook:
import { useSomeContext } from './someContext'
export const useCustomHook = () => {
const { reset, set } = useSomeContext()
useEffect(() => {
set()
return reset
}, [reset, set])
...
}
I am trying to test that the function set is called when the useCustomHook component renders. Here is my attempt at writing such a test:
import * as useSomeContextModule from '../someContext'
import { useCustomHook } from '../useCustomHook'
const mockReset = jest.fn()
const mockSet = jest.fn()
jest.mock('../someContext', () => {
const originalModule = jest.requireActual('../someContext')
return {
__esModule: true,
...originalModule,
useSomeContext: jest.fn(() => ({
reset: mockReset,
set: mockSet,
})),
}
})
test('set method is called when useCustomHook renders', () => {
useCustomHook()
expect(mockSet).toHaveBeenCalled()
})
But, i get this error:
TypeError: Cannot destructure property 'reset' of '(0 , _index.useSomeContext)(...)' as it is undefined.
7 |
8 | export const useCustomHook = () => {
> 9 | const { reset, set } = useSomeContext()
Instead of using jest.mock I used jest.spyOn:
import * as useSomeContextModule from '../someContext'
import { useCustomHook } from '../useCustomHook'
import { renderHook, cleanup } from '#testing-library/react-hooks'
const mockReset = jest.fn()
const mockSet = jest.fn()
beforeEach(() => {
jest.spyOn(useSomeContextModule, 'useSomeContext').mockReturnValue({
reset: mockReset,
set: mockSet,
})
})
test('set method is called when useCustomHook renders', () => {
renderHook(() => useCustomHook())
expect(mockSet).toHaveBeenCalledOnce()
})
test('reset method is called when useCustomHook unmounts', async () => {
renderHook(() => useCustomHook())
await cleanup()
expect(mockReset).toHaveBeenCalledOnce()
})

How to mock react custom hook return value as a module with Jest

I need to mock my custom hook when unit testing React component. I've read some stackoverflow answers but haven't succeeded in implementing it correctly.
I can't use useAuth without mocking it as it depends on server request and I'm only writing unit tests at the moment.
//useAuth.js - custom hook
const authContext = createContext();
function useProvideAuth() {
const [accessToken, setAccessToken] = useState('');
const [isAuthenticated, setAuthenticated] = useState(
accessToken ? true : false
);
useEffect(() => {
refreshToken();
}, []);
const login = async (loginCredentials) => {
const accessToken = await sendLoginRequest(loginCredentials);
if (accessToken) {
setAccessToken(accessToken);
setAuthenticated(true);
}
};
const logout = async () => {
setAccessToken(null);
setAuthenticated(false);
await sendLogoutRequest();
};
const refreshToken = async () => {
const accessToken = await sendRefreshRequest();
if (accessToken) {
setAccessToken(accessToken);
setAuthenticated(true);
} else setAuthenticated(false);
setTimeout(async () => {
refreshToken();
}, 15 * 60000 - 1000);
};
return {
isAuthenticated,
accessToken,
login,
logout
};
}
export function AuthProvider({ children }) {
const auth = useProvideAuth();
return <authContext.Provider value={auth}>{children}</authContext.Provider>;
}
AuthProvider.propTypes = {
children: PropTypes.any
};
const useAuth = () => {
return useContext(authContext);
};
export default useAuth;
The test I've written
//mainPage.test.js
import React from 'react';
import { render, screen } from '#testing-library/react';
import Main from '../main/mainPage';
describe('Main when !isAuthenticated', () => {
beforeEach(() => {
jest.mock('../auth/useAuth', () => {
const originalModule = jest.requireActual('../auth/useAuth');
return {
__esModule: true,
...originalModule,
default: () => ({
isAuthenticated: false,
login: jest.fn,
logout: jest.fn
})
};
});
});
afterEach(() => {
jest.resetModules();
});
it('displays image and login form', () => {
render(<Main />);
const image = screen.getByRole('img');
const form = document.querySelector('[data-testid=loginForm]');
expect(image).toBeInTheDocument();
expect(form).toBeInTheDocument();
});
});
However, I get this error.
TypeError: Cannot read properties of undefined (reading 'isAuthenticated')
7 |
8 | function Main() {
> 9 | const isAuthenticated = useAuth().isAuthenticated;
| ^
10 | const location = useLocation();
11 |
12 | if (isAuthenticated)
at Main (src/main/mainPage.js:9:26)
at renderWithHooks (node_modules/react-dom/cjs/react-dom.development.js:14985:18)...
I've been also trying to use spyOn but nothing helped. What exactly do I need to change to make the mock work?
The mocking should happen before any describe block:
import React from 'react';
import { render, screen } from '#testing-library/react';
import Main from '../main/mainPage';
jest.mock('../auth/useAuth', () => {
const originalModule = jest.requireActual('../auth/useAuth');
return {
__esModule: true,
...originalModule,
default: () => ({
isAuthenticated: false,
login: jest.fn,
logout: jest.fn
})
};
});
describe('Main when !isAuthenticated', () => {

Unable to mock react hooks with jest

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?

running mount() in react native Not Possible?

This post follows up with my previous question:
previous question
I have come across a test which requires me to run mount in react native. I have gone through the documentation in jest and have found that before running the test suite you specifically need to setup a test environment capable of running jsdom for mount to work:
The link to docs is:
testEnvironment
Because of it's horrible documentation. I can't figure out how to create the customEnvironment class and what after that? what do I do with the global object? How to use it in my test file which currently looks like:
describe('Estimate', () => {
test('Estimate component Exists', () => {
const onPressFunction = jest.fn()
const obj = shallow(
<Estimate onPress={onPressFunction} />
)
expect(obj.find('TextInput').exists()).toBe(true)
})
test('Estimate returns value on button press', () => {
const onPressFunction = jest.fn()
const obj = shallow(
<Estimate onPress={onPressFunction} />
)
obj.find('TextInput').first().simulate('keypress', { key: '1' })
obj.find('Button').first().props().onPress()
expect(onPressFunction.toHaveBeenCalledWith('1'))
})
})
I just made it work had to import three packages from npm:
jsdom
react-native-mock-renderer
jest-environment-jsdom
Also my setup.mjs file looks like:
// #note can't import shallow or ShallowWrapper specifically
import Enzyme from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
// eslint-disable-next-line
import { format } from 'prettier'
Enzyme.configure({ adapter: new Adapter() })
// Make Enzyme functions available in all test files without importing
global.shallow = Enzyme.shallow
Enzyme.ShallowWrapper.prototype.jsx = function jsx () {
const placeholder = '{ something: null }'
const obj = this.debug({ ignoreProps: false, verbose: true }).replace(/{\.\.\.}/g, placeholder)
return format(obj, {
parser: 'babylon',
filepath: 'test/setup.mjs',
trailingComma: 'all',
semi: false,
arrowParens: 'always',
})
.replace(new RegExp(placeholder, 'g'), '{...}')
.replace(';<', '<')
}
// the html function just throws errors so it's just reset to be the jsx function
Enzyme.ShallowWrapper.prototype.html = Enzyme.ShallowWrapper.prototype.jsx
jest.mock('react-native-device-info', () => {
return {
getDeviceLocale: () => 'en',
getDeviceCountry: () => 'US',
}
})
jest.mock('react-native-custom-tabs', () => ({
CustomTabs: {
openURL: jest.fn(),
},
}))
jest.mock('react-native-safari-view', () => ({
isAvailable: jest.fn(),
show: jest.fn(),
}))
const { JSDOM } = require('jsdom')
const jsdom = new JSDOM()
const { window } = jsdom
function copyProps (src, target) {
const props = Object.getOwnPropertyNames(src)
.filter((prop) => typeof target[prop] === 'undefined')
.map((prop) => Object.getOwnPropertyDescriptor(src, prop))
Object.defineProperties(target, props)
}
global.window = window
global.document = window.document
global.navigator = {
userAgent: 'node.js',
}
copyProps(window, global)
Enzyme.configure({ adapter: new Adapter() })
// Ignore React Web errors when using React Native
// allow other errors to propagate if they're relevant
const suppressedErrors = /(React does not recognize the.*prop on a DOM element|Unknown event handler property|is using uppercase HTML|Received `true` for a non-boolean attribute `accessible`|The tag.*is unrecognized in this browser)/
const realConsoleError = console.error
console.error = (message) => {
if (message.match(suppressedErrors)) {
return
}
realConsoleError(message)
}
require('react-native-mock-render/mock')
Test looks like:
test('Estimate returns value on button press', () => {
const onPressFunction = jest.fn()
const tree = mount(
<Estimate onPress={onPressFunction} />
)
console.log(tree.children().first().html())
})
Works like a charm!

React/Flux + Jasmine - Expected spy to have been called fails

I'm not really sure why this happens, I have a very simple Component and test, however, it fails on ✖ should call getState on TestStore. But as getStateFromStores DOES get called, it should be called as well, right? I am clueless atm.
import React, { PropTypes } from 'react'
import TestStore from '../stores/TestStore'
export default class TestComponent extends React.Component {
static propTypes = {
}
static getStateFromStores() {
return TestStore.getState()
}
constructor(props) {
super(props)
this.state = TestComponent.getStateFromStores()
}
render() {
return (
<div>
<img src='' alt=''/>
</div>
)
}
}
Test:
var React = require('react')
var TestUtils = require('react/lib/ReactTestUtils')
var Immutable = require('immutable')
const mockTestStoreData = Immutable.fromJS({
one: {
foo: 'bar'
},
two: {
bar: 'baz'
}
})
describe('TestComponent.jsx', () => {
var TestStore
var TestComponent
var TestComponentEl
var renderedRootElement
var renderedDOMNode
beforeEach(() => {
TestStore = require('../../stores/TestStore')
spyOn(TestStore, 'getState') // .and.returnValue(mockTestStoreData)
TestComponent = require('../TestComponent.jsx')
spyOn(TestComponent, 'getStateFromStores')
TestComponentEl = React.createElement(TestComponent)
renderedRootElement = TestUtils.renderIntoDocument(TestComponentEl)
renderedDOMNode = React.findDOMNode(renderedRootElement)
})
it('should be rendered within a div', () => {
expect(renderedDOMNode.tagName.toUpperCase()).toEqual('DIV')
})
it('should have a static getStateFromStores function', () => {
expect(TestComponent.getStateFromStores).toBeDefined()
})
it('should call getStateFromStores on construction', () => {
expect(TestComponent.getStateFromStores).toHaveBeenCalled()
})
it('should call getState on TestStore', () => {
expect(TestStore.getState).toHaveBeenCalled()
})
})
TestStore.getState() is supposed to be called from TestComponent.getStateFromStores(), but you are spying on TestComponent.getStateFromStores():
...
spyOn(TestComponent, 'getStateFromStores');
...
so the actual implementation is not being invoked during your tests. To spy on a function and also have it called, you could change to:
spyOn(TestComponent, 'getStateFromStores').and.callThrough();
Documentation for callThrough().
That being said, testing whether or not methods get called inside the module you are testing is perhaps too implementation specific. It's better to keep the test more cause-and-effect oriented. So my suggested solution would be to make the following adjustments:
var React = require('react')
var TestUtils = require('react/lib/ReactTestUtils')
var Immutable = require('immutable')
const mockTestStoreData = Immutable.fromJS({
one: {
foo: 'bar'
},
two: {
bar: 'baz'
}
})
describe('TestComponent.jsx', () => {
var TestStore
var TestComponent
var TestComponentEl
var renderedRootElement
var renderedDOMNode
beforeEach(() => {
TestStore = require('../../stores/TestStore')
spyOn(TestStore, 'getState').and.returnValue(mockTestStoreData);
TestComponent = require('../TestComponent.jsx')
// don't spyOn(TestComponent, 'getStateFromStores')
TestComponentEl = React.createElement(TestComponent)
renderedRootElement = TestUtils.renderIntoDocument(TestComponentEl)
renderedDOMNode = React.findDOMNode(renderedRootElement)
})
it('should be rendered within a div', () => {
expect(renderedDOMNode.tagName.toUpperCase()).toEqual('DIV')
})
it('should have a static getStateFromStores function', () => {
expect(TestComponent.getStateFromStores).toBeDefined()
})
// don't
//it('should call getStateFromStores on construction', () => {
// expect(TestComponent.getStateFromStores).toHaveBeenCalled()
//})
// don't
//it('should call getState on TestStore', () => {
// expect(TestStore.getState).toHaveBeenCalled()
//})
// instead
it( 'should get its initial state from TestStore', () => {
expect(TestStore.getState).toHaveBeenCalled(); // I'd say optional
expect( TestComponent.state ).toEqual( mockTestStoreData );
})
})
Now you're free to change the implementation and are testing what is important: the state of TestComponent after it is initialized.

Categories