I have this single-line function
export const makeReducerSetState = dispatch => type => payload =>
dispatch({ type, payload });
and this is how we call in the relative hook, where we are using useReducer
const wrapReducerDispatch = makeReducerSetState(stateDispatch);
const setSelectedSteps = wrapReducerDispatch(
EDITION_MODAL_REDUCER_TYPES.SET_SELECTED_STEPS
);
and after we call normally the action
const handleNext = useCallback(() => {
setSelectedSteps({
selectedIndex: selectedStep.selectedIndex + 1,
});
}, [selectedStep]);
I can't understand how can I test the first single-line function using Jest.
Literally, I tried to see several posts about curried function and reducer but I wasn't able to find a case like this one, can someone help me to understand?
How can I test this function down below ?
export const makeReducerSetState = dispatch => type => payload =>
dispatch({ type, payload });
One way is to use redux-mock-store
this library is designed to test the action-related logic
redux-mock-store saves all the dispatched actions inside the store instance. You can get all the actions by calling store.getActions(). Finally, you can use any assertion library to test the payload.
E.g.
export const makeReducerSetState = (dispatch) => (type) => (payload) => dispatch({ type, payload });
import { makeReducerSetState } from '.';
import configureStore from 'redux-mock-store';
const middlewares = [];
const mockStore = configureStore(middlewares);
describe('75334107', () => {
test('should pass', () => {
const initialState = {};
const store = mockStore(initialState);
const actionType = 'EDITION_MODAL_REDUCER_TYPES.SET_SELECTED_STEPS';
makeReducerSetState(store.dispatch)(actionType)(1);
expect(store.getActions()).toEqual([{ type: actionType, payload: 1 }]);
});
});
Test result:
PASS stackoverflow/75334107/index.test.ts (7.908 s)
75334107
✓ should pass (3 ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.ts | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 8.668 s, estimated 10 s
Related
Im trying to mock axios.get with spyOn call . this is the test
const { mount, shallowMount ,createLocalVue} = require('#vue/test-utils');
const BannersTakeovers =
require('~/vue_client/src/views/BannersTakeovers.vue').default;
const axios = require('axios');
const Vuetify = require('vuetify')
const Vuelidate =require('vuelidate');
const flushPromises = require('flush-promises')
const Vue = require("vue")
const mockPostList = [
{ id: 1, title: 'title1' },
{ id: 2, title: 'title2' }
]
Vue.use(Vuelidate)
let vuetify
beforeEach(() => {
vuetify = new Vuetify()
})
describe('Mounted BannersTakeovers', () => {
const localVue = createLocalVue()
localVue.use(Vuelidate)
const spy=jest.spyOn(axios,"get").mockReturnValue(mockPostList)
it('does a wrapper exist',async () => {
const wrapper = shallowMount(BannersTakeovers,{
localVue,
vuetify,
Vuelidate,
sync: true,
});
const button = wrapper.find(".testButton")
expect(button.text()).toBe("asyncFn")
let v$ = wrapper.vm.v$
await wrapper.vm.$nextTick()
await button.trigger('click')
wrapper.vm.$forceUpdate();
expect(spy).toHaveBeenCalledTimes(1)
})
})
module.exports= {
get: () => Promise.resolve({ data: 'value' })
}
i get this result:
Mounted BannersTakeovers › does a wrapper exist
expect(jest.fn()).toHaveBeenCalledTimes(expected)
Expected number of calls: 1
Received number of calls: 0
40 | wrapper.vm.$forceUpdate();
41 |
> 42 | expect(spy).toHaveBeenCalledTimes(1)
| ^
43 | // expect(asyncFn).toHaveBeenCalledTimes(1)
44 | })
45 | })
at Object.<anonymous> (__tests__/BannersTakeovers.spec.js:42:21)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 2.85 s
Ran all test suites.
npm ERR! Test failed. See above for more details.
If I console the result in my component it does run the axios code.
this is the relevant parts of my component:
<v-btn #click="getCampaigns()"
class="testButton"
>
asyncFn
</v-btn>
<script >
import axios from 'axios';
import moment from 'moment';
import AdminHeader from '../components/common/adminsHeader.vue';
import createCampain from '../components/common/createCampainDialog.vue';
import campainEditing from '../components/common/campainEditingDialog.vue';
import '#mdi/font/css/materialdesignicons.css';
getCampaigns(){
axios
.get(`http://mydomain:3000/bta/getAllCampaigns`, {})
.then((res) => {
if (res.data.length > 0) {
this.campaignData = res.data;
}
})
.catch((err) => console.log('getAllCampains', err));
}
},
async mounted() {
this.getCampaigns()
},
};
</script>
what am i doing wrong?
thanks for any help...
I have a simple TodoList component that uses a custom hook useTodos
import { useState } from 'react'
export const useTodos = (initialTodos = []) => {
const [todos, setTodos] = useState(initialTodos)
const addTodo = (value) => {
const updatedTodos = [...todos, value]
setTodos(updatedTodos)
}
const removeTodo = (index) => {
const updatedTodos = todos.filter((todo, i) => i !== index)
setTodos(updatedTodos)
}
return { todos, addTodo, removeTodo }
}
I would like to test the component with React Testing Library.
In order to do so, I want to mock the initial todos returned by the hook.
jest.mock('hooks/useTodos', () => ({
useTodos: () => ({
todos: ['Wake up', 'Go to work'],
}),
}))
But the methods addTodo and removeTodo are then undefined. On the other hand, when I mock them with jest.fn() they do not work anymore.
Is there any way to mock only todos and keep other methods working?
You can create a mocked useTodos hook with mock todos initial state based on the real useTodos hook.
hooks.js:
import { useState } from 'react';
export const useTodos = (initialTodos = []) => {
const [todos, setTodos] = useState(initialTodos);
const addTodo = (value) => {
const updatedTodos = [...todos, value];
setTodos(updatedTodos);
};
const removeTodo = (index) => {
const updatedTodos = todos.filter((todo, i) => i !== index);
setTodos(updatedTodos);
};
return { todos, addTodo, removeTodo };
};
index.jsx:
import React from 'react';
import { useTodos } from './hooks';
export default function MyComponent() {
const { todos, addTodo, removeTodo } = useTodos();
return (
<div>
{todos.map((todo, i) => (
<p key={i}>
{todo}
<button type="button" onClick={() => removeTodo(i)}>
Remove
</button>
</p>
))}
<button type="button" onClick={() => addTodo('have a drink')}>
Add
</button>
</div>
);
}
index.test.jsx:
import React from 'react';
import { render, screen, fireEvent } from '#testing-library/react';
import '#testing-library/jest-dom/extend-expect';
import MyComponent from '.';
jest.mock('./hooks', () => {
const { useTodos } = jest.requireActual('./hooks');
return {
useTodos: () => useTodos(['Wake up', 'Go to work']),
};
});
describe('69399677', () => {
test('should render todos', () => {
render(<MyComponent />);
expect(screen.getByText(/Wake up/)).toBeInTheDocument();
expect(screen.getByText(/Go to work/)).toBeInTheDocument();
});
test('should add todo', () => {
render(<MyComponent />);
fireEvent.click(screen.getByText(/Add/));
expect(screen.getByText(/have a drink/)).toBeInTheDocument();
});
test('should remove todo', () => {
render(<MyComponent />);
fireEvent.click(screen.getByText(/Go to work/).querySelector('button'));
expect(screen.queryByText(/Go to work/)).not.toBeInTheDocument();
});
});
test result:
PASS examples/69399677/index.test.jsx (8.788 s)
69399677
✓ should render todos (26 ms)
✓ should add todo (10 ms)
✓ should remove todo (4 ms)
-----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files | 100 | 0 | 100 | 100 |
hooks.js | 100 | 0 | 100 | 100 | 3
index.jsx | 100 | 100 | 100 | 100 |
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 9.406 s
My component is
const _onBoldClick = (e) => {
e.preventDefault();
onEnterText(RichUtils.toggleInlineStyle(editorState, 'BOLD'));
}
<button className = "text-button-style" onMouseDown = { e => { _onBoldClick(e)} }>B</button>
I am getting error of,
TypeError: e.preventDefault is not a function
74 |
75 | const _onUnderlineClick = (e) => {
> 76 | e.preventDefault();
| ^
77 | onEnterText(RichUtils.toggleInlineStyle(editorState, 'UNDERLINE'));
78 |
79 | }
at _onUnderlineClick (src/components/L3_Admin/L4_AnnouncementsTab/L4_AnnouncementsPage.js:76:11)
Here my tried test code is,
it('onmousedown U button', async () => {
let responseData = [
{
}]
const spy = jest.spyOn(axios, 'get');
const preventDefault = jest.fn();
const _onUnderlineClick = jest.fn();
spy.mockImplementation(async() => await act(() => Promise.resolve({ data: responseData })));
const component = mount( <Provider store= {store}>
<AnnouncementsTab /> </Provider>);
component.update();
// expect(component.find('button')).toHaveLength(6);
// expect(component.find('.text-button-style')).toHaveLength(3);
await component.find('.text-button-style').at(0).invoke('onMouseDown',{ preventDefault })(
{
nativeEvent: {
preventDefault,
e: jest.fn(),
_onUnderlineClick: (e)=>{}
}
},
1000
)
});
The function signature is .invoke(invokePropName)(...args) => Any. There is NO second parameter for .invoke() method. You should pass the mocked event object to the returned function of .invoke().
E.g.
MyComponent.jsx:
import React from 'react';
export function MyComponent() {
const _onBoldClick = (e) => {
e.preventDefault();
};
return (
<button
className="text-button-style"
onMouseDown={(e) => {
_onBoldClick(e);
}}
>
B
</button>
);
}
MyComponent.test.jsx:
import { mount } from 'enzyme';
import React from 'react';
import { MyComponent } from './MyComponent';
describe('67817812', () => {
it('onmousedown U button', async () => {
const preventDefault = jest.fn();
const component = mount(<MyComponent />);
component.find('.text-button-style').at(0).invoke('onMouseDown')({ preventDefault });
expect(preventDefault).toBeCalledTimes(1);
});
});
test result:
PASS examples/67817812/MyComponent.test.jsx (7.766 s)
67817812
✓ onmousedown U button (36 ms)
-----------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
MyComponent.jsx | 100 | 100 | 100 | 100 |
-----------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 8.754 s, estimated 9 s
I have the type:
export type AVAILABLE_API_STATUS =
| "Created"
| "Process"
| "Analisis"
| "Refused";
I would like to know how to create a state with the type above.
I tried to:
const [state] = React.useState<AVAILABLE_API_STATUS>("Refused");
But it asks for me to use typeof before the Type:
const [status] = React.useState<typeof AVAILABLE_API_STATUS>("Refused");
But it does not accept the 'Refused' value;
In the error message it says AVAILABLE_API_STATUS instead of the AVAILABLE_API you defined above. Is it possible you have a const AVAILABLE_API_STATUS and the type AVAILABLE_API and you mixed them up in the code?
Because this works for me:
export type AVAILABLE_API =
| 'Created'
| 'Process'
| 'Analisis'
| 'Refused';
export const TestComponent: React.FC = () => {
const [state, setState] = React.useState<AVAILABLE_API>('Refused');
return (
<button
type="button"
onClick={() => setState('Refused')}
>
{state}
</button>
);
};
I am working on a React form and have an onSubmit function to it. I only added below lines in onSubmit function.
const id = this.getErrorPositionById();
const errorPosition = document.getElementById(id).offsetTop; //CANNOT READ PROPERTY 'offsetTop' of null
window.scrollTo({
top: errorPosition,
behavior: "smooth"
});
And this is the onSubmit function.
public getErrorPositionById = () => {
const error = this.state.errors;
return Object.keys(error).find(id => error[id] != null);
};
public onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const id = this.getErrorPositionById();
const errorPosition = document.getElementById(id).offsetTop;
window.scrollTo({
top: errorPosition,
behavior: "smooth"
});
if (
!this.state.isValid ||
(!this.props.allowMultipleSubmits && this.state.isSubmitted)
) {
return;
}
Promise.all(this.validateFields())
.then((validationErrors: IValidationErrors[]) => {
this.setError(Object.assign({}, ...validationErrors), () => {
this.isFormValid() ? this.setSubmitted(e) : this.scrollFormToView();
});
})
.then(() => {
const newErrors = this.state.errors;
this.setState({ errors: { ...newErrors, ...this.props.apiErrors } });
});
};
Here is the test case
beforeEach(() => {
jest.clearAllMocks();
formFields = jest.fn();
onSubmit = jest.fn();
onValidate = jest.fn();
validate = jest.fn();
mockPreventDefault = jest.fn();
mockEvent = jest.fn(() => ({ preventDefault: mockPreventDefault }));
mockValidateAllFields = jest.fn(() => Promise);
mockChildFieldComponent = { validate };
instance = formWrapper.instance();
});
it("should not reValidate if form has been submitted already", () => {
instance.validateFields = mockValidateAllFields;
instance.setSubmitted();
expect(instance.state.isSubmitted).toBe(true);
instance.onSubmit(mockEvent());
expect(mockValidateAllFields).toHaveBeenCalledTimes(0);
});
The test case fails with error
TypeError: Cannot read property 'offsetTop' of null
on below line
const errorPosition = document.getElementById(id).offsetTop;
Can someone please help me understand how to eliminate this error.
You should make a stub for document.getElementById(id). For simple, I remove your business logic from the component.
E.g.
index.tsx:
import React, { Component } from 'react';
class SomeComponent extends Component {
state = {
errors: {
'#selector': {},
},
};
public getErrorPositionById = () => {
const error = this.state.errors;
return Object.keys(error).find((id) => error[id] != null);
};
public onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const id = this.getErrorPositionById() as string;
const errorPosition = document.getElementById(id)!.offsetTop;
window.scrollTo({
top: errorPosition,
behavior: 'smooth',
});
};
public render() {
return (
<div>
<form onSubmit={this.onSubmit}></form>
</div>
);
}
}
export default SomeComponent;
index.spec.tsx:
import React from 'react';
import { shallow } from 'enzyme';
import SomeComponent from './';
describe('SomeComponent', () => {
afterEach(() => {
jest.resetAllMocks();
});
it('should handle submit correctly', async () => {
const mElement = { offsetTop: 123 };
document.getElementById = jest.fn().mockReturnValueOnce(mElement);
window.scrollTo = jest.fn();
const wrapper = shallow(<SomeComponent></SomeComponent>);
const mEvent = { preventDefault: jest.fn() };
wrapper.find('form').simulate('submit', mEvent);
expect(mEvent.preventDefault).toHaveBeenCalledTimes(1);
expect(document.getElementById).toBeCalledWith('#selector');
expect(window.scrollTo).toBeCalledWith({ top: 123, behavior: 'smooth' });
});
});
Unit test result with coverage report:
PASS src/stackoverflow/53352420/index.spec.tsx (12.859s)
SomeComponent
✓ should handle submit correctly (20ms)
-----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.tsx | 100 | 100 | 100 | 100 | |
-----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 14.751s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/53352420