vue2-jest test utils doesn't read the axios.get mock - javascript

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...

Related

custom hook testing: when testing, code that causes React state updates should be wrapped into act(...):

Following this tutorial https://www.richardkotze.com/coding/mocking-react-hooks-unit-testing-jest, but getting this error even though the test passes, why is this error occurring and is there something missing from the test? code copied here for convenience
PASS src/use-the-fetch.spec.js
● Console
console.error
Warning: An update to TestComponent inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act
at TestComponent (~/Documents/projects/my-app/node_modules/#testing-library/react-hooks/lib/helpers/createTestHarness.js:22:5)
at Suspense
at ErrorBoundary (~/Documents/projects/my-app/node_modules/react-error-boundary/dist/react-error-boundary.cjs.js:59:35)
5 | async function fetchData() {
6 | const data = await getStarWars(path); // being mocked
> 7 | setResult({ loading: false, data });
| ^
8 | }
9 | useEffect(() => {
10 | fetchData();
at console.error (node_modules/#testing-library/react-hooks/lib/core/console.js:19:7)
at printWarning (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:68:30)
at error (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:44:5)
at warnIfNotCurrentlyActingUpdatesInDEV (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:15034:9)
at setResult (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7129:9)
at fetchData (src/use-the-fetch.js:7:5)
use-the-fetch.spec.js
import { renderHook } from "#testing-library/react-hooks";
import { useTheFetch } from "./use-the-fetch";
import { getStarWars } from "./base-fetch";
jest.mock("./base-fetch");
describe("use the fetch", () => {
it("initial data state is loading and data empty", () => {
const { result } = renderHook(() => useTheFetch("people"));
expect(result.current).toStrictEqual({ loading: true, data: null });
});
it("data is fetched and loading is complete", async () => {
const fakeSWData = { result: [{ name: "Luke Skywalker" }] };
getStarWars.mockResolvedValue(fakeSWData);
const { result, waitForNextUpdate } = renderHook(() =>
useTheFetch("people")
);
await waitForNextUpdate();
expect(getStarWars).toBeCalledWith("people");
expect(result.current).toStrictEqual({
loading: false,
data: fakeSWData,
});
});
});
use-the-fetch.js
import { useState, useEffect } from "react";
import { getStarWars } from "./base-fetch";
export function useTheFetch(path) {
const [result, setResult] = useState({ loading: true, data: null });
async function fetchData() {
const data = await getStarWars(path); // being mocked
setResult({ loading: false, data });
}
useEffect(() => {
fetchData();
}, []);
return result;
}
base-fetch.js
const BASE_URL = "https://swapi.co/api/";
export async function baseFetch(url, options = {}) {
const response = await fetch(url, options);
return await response.json();
}
export const getStarWars = async (path) => baseFetch(BASE_URL + path);

How do I mock only one value returned by a custom hook?

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

How to test functional component with async callback inside useEffect using snapshots

I'm trying to write unit tests on my component, it looks like this.
export const myComponent = ({text, list, getData = transport.getData}) => {
const [rows, setRows] = React.useState([]);
React.useEffect(() => {
const fetchData = async () => {
const rows = await getData(list);
setRows(rows);
};
fetchData();
}, [list]);
if (rows.length === 0) {
return null;
}
return (
// some JSX
);
};
The problem is that component fetches data via async function, so it will be called after the component check if rows is empty and return null.
if (rows.length === 0) {
return null;
}
I mocked getData so it should return some values. But still, I couldn't understand how I should cover this component with unit testing. I suppose it should be a snapshot, perhaps it is not right.
My test:
import React from 'react';
import {myComponent} from '../components/myComponent';
import renderer from 'react-test-renderer';
describe('myComponent', () => {
test('should renders correctly', async () => {
const mock = {
text: 'text',
list: [],
getData: () =>
Promise.resolve([
{
// ...
},
]),
};
const component = renderer.create(<myComponent text={mock.text}
list={mock.list} getData={mock.getData}/>);
let popup = component.toJSON();
expect(popup).toMatchSnapshot();
});
});
Here is the unit test solution:
index.tsx:
import React from 'react';
const transport = {
async getData(list) {
return [{ id: 1 }];
}
};
export const MyComponent = ({ text, list, getData = transport.getData }) => {
const [rows, setRows] = React.useState<any[]>([]);
React.useEffect(() => {
console.count('useEffect');
const fetchData = async () => {
console.count('fetchData');
const newRows = await getData(list);
setRows(newRows);
};
fetchData();
}, [list]);
if (rows.length === 0) {
return null;
}
return <div>rows count: {rows.length}</div>;
};
index.spec.tsx:
import React from 'react';
import { MyComponent } from './';
import renderer, { act } from 'react-test-renderer';
describe('myComponent', () => {
test('should renders correctly', async () => {
const mProps = {
text: 'text',
list: [],
getData: jest.fn().mockResolvedValueOnce([{ id: 1 }, { id: 2 }, { id: 3 }])
};
let component;
await act(async () => {
component = renderer.create(<MyComponent {...mProps}></MyComponent>);
});
expect(component.toJSON()).toMatchSnapshot();
});
});
Unit test result:
PASS src/stackoverflow/57778786/index.spec.tsx
myComponent
✓ should renders correctly (29ms)
console.count src/stackoverflow/57778786/index.tsx:13
useEffect: 1
console.count src/stackoverflow/57778786/index.tsx:15
fetchData: 1
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 1 passed, 1 total
Time: 3.557s, estimated 8s
index.spec.tsx.snap:
// Jest Snapshot v1
exports[`myComponent should renders correctly 1`] = `
<div>
rows count:
3
</div>
`;
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/57778786

Getting 'arr[Symbol.iterator] is not a function' when using custom api hook

I've written a custom hook that makes an api call the returns the result and an error if present. When trying to right a Jest Test for the component that is using the custom hooks I get 'arr[Symbol.iterator] is not a function' or 'Cannot read property 'Symbol(Symbol.iterator)' of undefined' before the hook when using Jest shallow()
I felt this was an issue with the api response working with a promise so I tried various different approaches around .then or 'wait' for aysnc. This doesn't resolve the issue
My Custom api hook
/* Packages */
import { useEffect, useState } from 'react';
export const useApiCall = (endPoint) => {
const [hasError, setHasErrors] = useState(false);
const [fetchData, setFetchData] = useState(null);
const getData = async () => {
try {
const response = await fetch(endPoint);
if (!response.ok) {
throw Error(response.statusText);
}
const responseJSON = await response.json();
setFetchData(responseJSON.data);
}
catch (error) {
setHasErrors(true);
}
}
useEffect(() => {
getData()
},[])
return [fetchData, hasError]
}
My component using the hook
/* Packages */
import React, { useState } from 'react';
/* Utils */
import { useApiCall } from '../../utils/useApiCall';
/* Components */
import FooterStructure from './FooterStructure'
const Footer = () => {
const [footerLinks, error] = useApiCall('http://www.mocky.io/v2/5d7678f93200000362297bae');
const [visible, setVisible] = useState();
const showContent = () => {
setVisible(!visible);
}
const componentProps = {
footerLinks : footerLinks,
error : error,
visible : visible,
showContent : showContent
};
return (
<FooterStructure {...componentProps} />
)
};
export default Footer;
My Jest test without/with promises
import { shallow, mount } from 'enzyme';
import Footer from '.';
import mock from './__mocks__/FooterLinks';
import { useApiCall } from '../../utils/useApiCall';
import { testHook } from '../../utils/TestUtils';
jest.mock('../../utils/useApiCall');
let footerData,
wrapper;
beforeEach(() => {
wrapper = shallow(<Footer />);
testHook(async () => {
useApiCall.mockImplementation(() => Promise.resolve({ data: mock }));
useApiCall().then((resolve)=>{
footerData = resolve;
})
// This promise gives the same error
// .then(()=>{
// wrapper = shallow(<Footer />);
// })
// .catch((error)=>{
// console.log(error);
// });
});
});
describe('Footer links', () => {
it('should have 3 links', () => {
expect(footerData.data.length).toEqual(3);
});
});
The error
Footer links
✕ should have 3 links (46ms)
● Footer links › should have 3 links
TypeError: Cannot read property 'Symbol(Symbol.iterator)' of undefined
8 | import FooterStructure from './FooterStructure'
9 |
> 10 | const Footer = () => {
| ^
11 | const [footerLinks, error] = useApiCall('http://www.mocky.io/v2/5d7678f93200000362297bae');
12 | const [visible, setVisible] = useState();
13 |
at _iterableToArrayLimit (node_modules/babel-preset-react-app/node_modules/#babel/runtime/helpers/iterableToArrayLimit.js:8:22)
at _slicedToArray (node_modules/babel-preset-react-app/node_modules/#babel/runtime/helpers/slicedToArray.js:8:33)
at Footer (src/global/Footer/index.js:10:22)
at ReactShallowRenderer.render (node_modules/react-test-renderer/cjs/react-test-renderer-shallow.development.js:794:32)
at render (node_modules/enzyme-adapter-react-16/src/ReactSixteenAdapter.js:636:55)
at fn (node_modules/enzyme-adapter-utils/src/Utils.js:99:18)
at Object.render (node_modules/enzyme-adapter-react-16/src/ReactSixteenAdapter.js:636:20)
at new render (node_modules/enzyme/src/ShallowWrapper.js:397:22)
at shallow (node_modules/enzyme/src/shallow.js:10:10)
at Object.<anonymous>.beforeEach (src/global/Footer/Footer.test.js:14:15)
● Footer links › should have 3 links
TypeError: Cannot read property 'data' of undefined
24 | describe('Footer links', () => {
25 | it('should have 3 links', () => {
> 26 | expect(footerData.data.length).toEqual(3);
| ^
27 | });
28 | });
29 |
at Object.data (src/global/Footer/Footer.test.js:26:27)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 0.66s, estimated 1s
Ran all test suites related to changed files.
Watch Usage: Press w to show more.```
My jest test with promise

how to write unit test for multiple conditions in redux-thunk?

I have a thunk like the below
export const goToNewExperience = (request?: sessionRequest): ThunkAction => {
return async (dispatch: Dispatch, getState: GetState) => {
const state = getState();
const isNewExp = state.isNewExp;
if (isNewExp) {
dispatch(updateExperience({
type: UPDATE_EXP,
'NewExperience'
}))
} else if (request && request.isError) {
dispatch(updateExperience({
type: UPDATE_EXP,
'ErrorExperience'
}));
}
};
};
how to test the other action dispatchers in one redux-thunk based on a condition ? any best practices?
I wrote like this but looking for best practices
it('should update exp with New Exp', done => {
const store = createStoreWithState();
const session = {isNewExp:true};
store.dispatch(updateSession(session));
const dispatch = jest.fn();
goToNewExperience()(dispatch, () => store.getState()).then(_ => {
expect((dispatch.mock.calls[0][0]: any).type).toEqual(UPDATE_EXP);
expect((dispatch.mock.calls[0][0]: any).payload).toEqual('NewExperience');
done();
});
});
it('should update exp with Error Exp', done => {
const store = createStoreWithState();
const session = {isNewExp:false};
store.dispatch(updateSession(session));
const dispatch = jest.fn();
goToNewExperience({isError:true})(dispatch, () => store.getState()).then(_ => {
expect((dispatch.mock.calls[0][0]: any).type).toEqual(UPDATE_EXP);
expect((dispatch.mock.calls[0][0]: any).payload).toEqual('ErrorExperience');
done();
});
});
Here is the best practice for write unit testing for actionCreators of redux:
actionCreators.ts:
import { ThunkAction } from 'redux-thunk';
import { Dispatch, AnyAction } from 'redux';
export const UPDATE_EXP = 'UPDATE_EXP';
export const updateExperience = action => ({ type: action.type, payload: { experience: action.experience } });
export interface ISessionRequest {
isError: boolean;
}
type GetState<S = IState> = () => S;
export interface IState {
isNewExp: boolean;
}
export const goToNewExperience = (request?: ISessionRequest): ThunkAction<any, IState, {}, AnyAction> => {
return async (dispatch: Dispatch, getState: GetState<IState>) => {
const state = getState();
const isNewExp = state.isNewExp;
if (isNewExp) {
dispatch(
updateExperience({
type: UPDATE_EXP,
experience: 'NewExperience'
})
);
} else if (request && request.isError) {
dispatch(
updateExperience({
type: UPDATE_EXP,
experience: 'ErrorExperience'
})
);
}
};
};
actionCreators.spec.ts:
import { goToNewExperience, IState, UPDATE_EXP, ISessionRequest } from './actionCreators';
import createMockStore from 'redux-mock-store';
import { AnyAction } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
import thunk from 'redux-thunk';
const middlewares = [thunk];
const mockStore = createMockStore<IState, ThunkDispatch<IState, any, AnyAction>>(middlewares);
describe('goToNewExperience', () => {
it('update new experience', () => {
const initialState = { isNewExp: true };
const store = mockStore(initialState);
return store.dispatch(goToNewExperience()).then(() => {
expect(store.getActions()).toEqual([{ type: UPDATE_EXP, payload: { experience: 'NewExperience' } }]);
});
});
it('update error experience', () => {
const initialState = { isNewExp: false };
const store = mockStore(initialState);
const request: ISessionRequest = { isError: true };
return store.dispatch(goToNewExperience(request)).then(() => {
expect(store.getActions()).toEqual([{ type: UPDATE_EXP, payload: { experience: 'ErrorExperience' } }]);
});
});
it('do nothing', () => {
const initialState = { isNewExp: false };
const store = mockStore(initialState);
const request: ISessionRequest = { isError: false };
return store.dispatch(goToNewExperience(request)).then(() => {
expect(store.getActions()).toEqual([]);
});
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/49824394/actionCreators.spec.ts (5.028s)
goToNewExperience
✓ update new experience (10ms)
✓ update error experience (1ms)
✓ do nothing (1ms)
-------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-------------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
actionCreators.ts | 100 | 100 | 100 | 100 | |
-------------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 6.09s, estimated 7s
Here is the completed demo: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/49824394

Categories

Resources