Jest: Mock a _HOC_ or _curried_ function - javascript

Given the following function:
./http.js
const http = {
refetch() {
return (component) => component;
}
}
I would like to mock the function in a test as follows:
./__tests__/someTest.js
import { refetch } from './http';
jest.mock('./http', () => {
return {
refetch: jest.fn();
}
}
refetch.mockImplementation((component) => {
// doing some stuff
})
But I'm receiving the error
TypeError: _http.refetch.mockImplementation is not a function
How can I mock the refetch function in the given example?
update:
When I modify the mock function slightly to:
jest.mock(
'../http',
() => ({ refetch: jest.fn() }),
);
I get a different error:
TypeError: (0 , _http.refetch)(...) is not a function
My guess it's something with the syntax where the curried function (or HOC function) is not mapped properly. But I don't know how to solve it.
Some of the real code I'm trying to test.
Note: The example is a bit sloppy. It works in the application. The example given is to give an idea of the workings.
./SettingsContainer
// ...some code
return (
<FormComponent
settingsFetch={settingsFetch}
settingsPutResponse={settingsPutResponse}
/>
);
}
const ConnectedSettingsContainer = refetch(
({
match: { params: { someId } },
}) => ({
settingsFetch: {
url: 'https://some-url.com/api/v1/f',
},
settingsPut: (data) => ({
settingsPutResponse: {
url: 'https://some-url.com/api/v1/p',
}
}),
}),
)(SettingsContainer);
export default ConnectedSettingsContainer;
Then in my component I am getting the settingsPutResponse via the props which react-refetch does.
I want to test if the user can re-submit a form after the server has responded once or twice with a 500 until a 204 is given back.
./FormComponent
// ...code
const FormComp = ({ settingsResponse }) => {
const [success, setSuccess] = useState(false);
useEffect(() => {
if (settingsResponse && settingsResponse.fulfilled) {
setSuccess(true);
}
}, [settingsResponse]);
if (success) {
// state of the form wil be reset
}
return (
<form>
<label htmlFor"username">
<input type="text" id="username" />
<button type="submit">Save</button>
</form>
)
};

The first question to ask yourself about mocking is "do I really need to mock this?" The most straightforward solution here is to test "component" directly instead of trying to fake out an http HOC wrapper around it.
I generally avoid trying to unit test things related to I/O. Those things are best handled with functional or integration tests. You can accomplish that by making sure that, given same props, component always renders the same output. Then, it becomes trivial to unit test component with no mocks required.
Then use functional and/or integration tests to ensure that the actual http I/O happens correctly
To more directly answer you question though, jest.fn is not a component, but React is expecting one. If you want the mock to work, you must give it a real component.
Your sample code here doesn't make sense because every part of your example is fake code. Which real code are you trying to test? I've seen gigantic test files that never actually exercize any real code - they were just testing an elaborate system of mocks. Be careful not to fall into that trap.

Related

Jest custom testing API, how to correct code frame

I'm looking to simplify my project's testing API where I am aiming for something like this:
testThing((t) => {
t(33);
t(44);
t(42);
})
Now I don't know how to get Jest to show the correct code frames for failed expect's. This is my current stab at an implementation:
const testThing = (callback: any) => {
callback((n: any) => {
test(n.toString(), () => {
expect(n).toBe(42);
});
});
};
Which results in the testThing definition to be shown for every failed test case. Here's a replit if you want to see it in action: https://replit.com/#grgr/jest-frame-issue#thing.test.js

How to reset Cypress window.location.href after test

I am testing some ui that on click updates the window.location.href. I have two tests, the first one works, but the second starts in the location set by the ui in the previous test. This is wrong and stops test two from starting on the right page.
How can I reset the window.location.href or just the Cypress browser location in general back to where it was at the beginning of the first test?
I have checked the window.location.href at the start of the second test and it looks autogenerated and so I don't think wise to try and hardcode that value into window.location.href at the start of the second test.
Looking for something I can run at afterEach.
Test
it.only('should send asynchronous analytics event after provider selection click', () => {
rewire$useFlag(() => true);
cy.location().then((location) => console.log('window !!'));
const analyticsAsyncStub = cy.stub().as('sendAnalyticsAsyncStub');
rewire$sendAnalyticsAsync(analyticsAsyncStub);
// #NOTE hacking browser detection so required provider options are availiable
cy.window().then(($window) => {
console.log('window !!', $window.location.href);
($window as any).chrome = {};
($window as any).chrome.runtime = {
sendMessage() {
'mock function';
},
};
});
mountFixtureWithProviders({ children: <ProviderSelection flagsConfig={defaultFlags} /> });
cyGetByTestId('provider--metaMask').click();
cy.get('#sendAnalyticsAsyncStub').should(
'have.been.calledWithMatch',
analyticsUtilsModule.createButtonEvent(ButtonEventName.providerSelectionPressed),
);
});
mountFixtureWithProviders function
export const mountFixtureWithProviders = ({
children,
mountInsideVisualMock = true,
setErrorLog = () => ({}),
renderErrorScreens,
}: {
children: ReactNode;
mountInsideVisualMock?: boolean;
setErrorLog?: SetErrorLog;
renderErrorScreens?: boolean;
}) => {
const RouterMockedChildren = () => <MemoryRouter>{children}.
</MemoryRouter>;
const ProvidedChildren = () =>
renderErrorScreens ? (
<DemoAppToRenderErrorMessages>
<RouterMockedChildren />
</DemoAppToRenderErrorMessages>
) : (
<LinkUiCoreContext.Provider value={{ setErrorLog,
imageResizerServiceUrl: DEV_IMAGE_RESIZER_SERVICE_URL }}>
<RouterMockedChildren />
</LinkUiCoreContext.Provider>
);
return mount(mountInsideVisualMock ? linkPageVisualMock({
children: <ProvidedChildren /> }) : <ProvidedChildren />);
};
Thank you.
Usually you can just do a visit in a beforeEach() to get a clean start for each test
beforeEach(() => {
cy.visit('/')
})
There's also cy.go('back') which you can run at the end of the first test.
But be aware that a fail in test 1 will then fail test 2 because the navigation won't happen - same applies to adding into afterEach().
In Cypress window is the test runner window, but you can access the app window with
cy.window().then(win => console.log(win.location.href))
or the location directly with
cy.location().then(loc => console.log(loc.href))
Don't use cy.log() for debugging, use console.log() as there are side-effects to cy.log() that may give you the wrong debugging info.
(mountFixtureWithProviders({ children: <ProviderSelection flagsConfig={{}} /> }) looks like a component test, so yes I agree that should give you a fresh start for each test.
Can you add the two tests and also mountFixtureWithProviders in the question to give the full picture please.

React async function is not a function

In one of my components I have the following functions:
addNewIndicator(attrs = {}) {
const value = attrs.value || 'Indicator'
const type = attrs.type || 'Generic Type'
this.createIndicator(value).then(
console.log('Indicator Created.')
)
}
async createIndicator(value) {
await this.props.createIndicatorMutation({
variables: {
value
},
update: (store, { data: { indicator }} ) => {
const data = store.readQuery({ query: INDICATOR_FEED_QUERY })
data.indicatorFeed.splice(0, 0, indicator)
store.writeQuery({
query: INDICATOR_FEED_QUERY,
data,
})
}
})
}
addNewIndicator() is triggered on a button click. When it runs, I get the following error:
TypeError: this.createIndicator is not a function
It is pointing to this line:
this.createIndicator(value).then(
I've done quite a bit of Googling, but haven't been able to figure out why this is the case. My understanding is that async functions can be called like that, but perhaps I'm missing something. Sorry if this is a silly question, I'm still learning React!
Also, I created the project using create-react-app and haven't modified it much other than adding some packages. Thanks for any help!
Edit to add how it is called. It is called from a child component props:
<Button primary onClick={this.handleAddSelectionClick}>Add Selected As Indicator</Button>
and handleAddSelectionClick:
handleAddSelectionClick = () => {
...snip...
this.props.addNewIndicator({
value: new_indicator_str,
})
}
try to validate that your 2 methods using the same context (this) -
you might need to do something like that in the constructor:
this.addNewIndicator = this.addNewIndicator.bind(this);
this.createIndicator = this.createIndicator.bind(this);
You probably just forgot to bind “this”. As you passed addNewIndicator as a callback, it lost its context. In this article several methods of binding callbacks are described, with all pros and cons of each.
https://reactjs.org/docs/handling-events.html

jest.mock is not a function in react?

Could you please tell me how to test componentDidMount function using enzyme.I am fetching data from the server in componentDidMount which work perfectly.Now I want to test this function.
here is my code
https://codesandbox.io/s/oq7kwzrnj5
componentDidMount(){
axios
.get('https://*******/getlist')
.then(res => {
this.setState({
items : res.data.data
})
})
.catch(err => console.log(err))
}
I try like this
it("check ajax call", () => {
const componentDidMountSpy = jest.spyOn(List.prototype, 'componentDidMount');
const wrapper = shallow(<List />);
});
see updated code
https://codesandbox.io/s/oq7kwzrnj5
it("check ajax call", () => {
jest.mock('axios', () => {
const exampleArticles:any = {
data :{
data:['A','B','c']
}
}
return {
get: jest.fn(() => Promise.resolve(exampleArticles)),
};
});
expect(axios.get).toHaveBeenCalled();
});
error
You look like you're almost there. Just add the expect():
expect(componentDidMountSpy).toHaveBeenCalled();
If you need to check if it was called multiple times, you can use toHaveBeenCalledTimes(count).
Also, be sure to mockRestore() the mock at the end to make it unmocked for other tests.
List.prototype.componentDidMount.restore();
To mock axios (or any node_modules package), create a folder named __mocks__ in the same directory as node_modules, like:
--- project root
|-- node_modules
|-- __mocks__
Inside of there, make a file named <package_name>.js (so axios.js).
Inside of there, you'll create your mocked version.
If you just need to mock .get(), it can be as simple as:
export default { get: jest.fn() }
Then in your code, near the top (after imports), add:
import axios from 'axios';
jest.mock('axios');
In your test, add a call to axios.get.mockImplementation() to specify what it'll return:
axios.get.mockImplementation(() => Promise.resolve({ data: { data: [1, 2, 3] } });
This will then make axios.get() return whatever you gave it (in this case, a Promise that resolves to that object).
You can then do whatever tests you need to do.
Finally, end the test with:
axios.get.mockReset();
to reset it to it's default mocked implementation.

Adding a loading animation when loading in ReactJS

I would like to add a loading animation to my website since it's loading quite a bit when entering the website. It is built in ReactJS & NodeJS, so I need to know specifically with ReactJS how to add a loading animation when initially entering the site and also when there is any loading time when rendering a new component.
So is there a way to let people on my website already, although it's not fully loaded, so I can add a loading page with some CSS3 animation as a loading screen.
The question is not really how to make a loading animation. It's more about how to integrate it into ReactJS.
Thank you very much.
Since ReactJS virtual DOM is pretty fast, I assume the biggest load time is due to asynchronous calls. You might be running async code in one of the React lifecycle event (e.g. componentWillMount).
Your application looks empty in the time that it takes for the HTTP call. To create a loader you need to keep the state of your async code.
Example without using Redux
We will have three different states in our app:
REQUEST: while the data is requested but has not loaded yet.
SUCCESS: The data returned successfully. No error occurred.
FAILURE: The async code failed with an error.
While we are in the request state we need to render the spinner. Once the data is back from the server, we change the state of the app to SUCCESS which trigger the component re-render, in which we render the listings.
import React from 'react'
import axios from 'axios'
const REQUEST = 'REQUEST'
const SUCCESS = 'SUCCESS'
const FAILURE = 'FAILURE'
export default class Listings extends React.Component {
constructor(props) {
super(props)
this.state = {status: REQUEST, listings: []}
}
componentDidMount() {
axios.get('/api/listing/12345')
.then(function (response) {
this.setState({listing: response.payload, status: SUCCESS})
})
.catch(function (error) {
this.setState({listing: [], status: FAILURE})
})
}
renderSpinner() {
return ('Loading...')
}
renderListing(listing, idx) {
return (
<div key={idx}>
{listing.name}
</div>
)
}
renderListings() {
return this.state.listing.map(this.renderListing)
}
render() {
return this.state.status == REQUEST ? this.renderSpinner() : this.renderListings()
}
}
Example using Redux
You can pretty much do the similar thing using Redux and Thunk middleware.
Thunk middleware allows us to send actions that are functions. Therefore, it allows us to run an async code. Here we are doing the same thing that we did in the previous example: we keep track of the state of asynchronous code.
export default function promiseMiddleware() {
return (next) => (action) => {
const {promise, type, ...rest} = action
if (!promise) return next(action)
const REQUEST = type + '_REQUEST'
const SUCCESS = type + '_SUCCESS'
const FAILURE = type + '_FAILURE'
next({...rest, type: REQUEST})
return promise
.then(result => {
next({...rest, result, type: SUCCESS})
return true
})
.catch(error => {
if (DEBUG) {
console.error(error)
console.log(error.stack)
}
next({...rest, error, type: FAILURE})
return false
})
}
}

Categories