React Jest Testing onSubmit - reactjs

I am new to react and jest. I have been looking everywhere for testing but I cannot find anything that is helpful. This is partially because I am so new to it, I havent a clue where to start. So bear with me, please.
I have an add to cart file which renders a form with a button inside it. The button is another component, so I'm not looking to test it. I have to test the onSubmit function for the form. Any thoughts? References?
Here is my code so far for the test:
describe('AddToCart', () => {
const React = require('react');
const BaseRenderer = require('react/lib/ReactTestUtils');
const Renderer = BaseRenderer.createRenderer();
const ReactTestUtils = require('react-addons-test-utils');
const AddToCart = require('../index.js').BaseAddToCart;
it('Will Submit', () => {
formInstance = ReactTestUtils.renderIntoDocument(<AddToCart product="" quantity=""/>);
expect(ReactTestUtils.Simulate.onSubmit(formInstance)).toBeCalled();
});
});
I'm getting this error:
Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.

Consider using Jest with Enzyme. I think it's good stack for unit testing in react.
Also, I made a sample test that tests onSubmit function in LogIn component.
import React from 'react';
import {shallow} from 'enzyme';
import LogIn from './LogIn';
describe('<LogIn />', () => {
const testValues = {
username: 'FOO',
password: 'BAZ',
handleSubmit: jest.fn(),
};
it('Submit works', () => {
const component = shallow(
<LogIn {...testValues} />
);
component.find('#submitButton').simulate('click');
expect(testValues.handleSubmit).toHaveBeenCalledTimes(1);
expect(testValues.handleSubmit).toBeCalledWith({username: testValues.username, password: testValues.password});
});
});

Related

FE: Unit testing window.location.href changing on button click (test fails)

I am trying to test when the window.location.href changes after a button is clicked using react-testing-library. I have seen examples online where you manually update window.location.href inside of a test case as so window.location.href = 'www.randomurl.com' and then follow it with expect(window.location.href).toEqual(www.randomurl.com). While this indeed will pass, I want to avoid this as I'd rather simulate the user actions instead of injecting the new value into the test. If I do that, even if I remove my button click (which is what will actually trigger the function call) the expect will still pass because I have anyway manually updated the window.location.href in my test
What I've opted for is having goToThisPage func (which will redirect the user) to be placed outside of my functional component. I then mock goToThisPage in my test file and in my test case check whether it has been called. I do know that the goToThisPage is being triggered because I included a console.log and when I run my tests I see it in my terminal. Nonetheless, the test still fails. I have been playing around with both spyOn and jest.doMock/mock with no luck
component.js
import React from 'react'
import { ChildComponent } from './childcomponent';
export const goToThisPage = () => {
const url = '/url'
window.location.href = url;
console.log('reached');
};
export const Component = () => {
return (<ChildComponent goToThisPage={ goToThisPage }/>)
}
export default Component;
Test file:
import * as Component from './component'
import userEvent from '#testing-library/user-event';
jest.doMock('./component', () => ({
goToThisPage: jest.fn(),
}));
describe('goToThisPage', () => {
test('should call goToThisPage when button is clicked', async () => {
const goToThisPageSpy = jest.spyOn(Component, 'goToThisPage');
const { container, getByTestId } = render(<Component.Component />);
userEvent.click(screen.getByTestId('goToThisPage')); // this is successfully triggered (test id exists in child component)
expect(goToThisPageSpy).toHaveBeenCalled();
// expect(Component.goToThisPage()).toHaveBeenCalled(); this will fail and say that the value must be a spy or mock so I opted for using spy above
});
});
Note: when I try to just do jest.mock I got this error Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
When testing out with jest.doMock the error disappeared but the actual test fails.
I am open to hear more refined ideas of solving my issue if someone believes this solution could be improved. Thanks in advance
Edit:
This is another approach I have tried out
import { Component, goToThisPage } from './component'
import userEvent from '#testing-library/user-event';
describe('goToThisPage', () => {
test('should call goToThisPage when button is clicked', async () => {
const goToThisPageSpy = jest.spyOn(Component, 'goToThisPage');
// I am not certain what I'd put as the first value in the spy. Because `goToThisPage` is an external func of <Component/> & not part of the component
const { container, getByTestId } = render(<Component />);
userEvent.click(screen.getByTestId('goToThisPage'));
expect(goToThisPageSpy).toHaveBeenCalled();
});
});
Save yourself the headache and split the goToThisPage function into its own file. You seem to be mocking the goToThisPage function fine but when the Component is rendered with react testing library it doesn't seem render with the mocked function but defaults to what the function would normally do. This easiest way would be just to mock the function from its own file. If you truly want to keep the function in the same file you will need to make some adjustments, see (example #2) but I do not recommend this path.
See below for examples
Example 1: (Recommended) Split function into it's own file
Component.spec.jsx
import React from "react";
import Component from "./Component";
import { render, screen } from "#testing-library/react";
import userEvent from "#testing-library/user-event";
import * as goToThisPage from "./goToThisPage";
jest.mock('./goToThisPage');
describe("goToThisPage", () => {
test("should call goToThisPage when button is clicked", async () => {
const goToThisPageSpy = jest.spyOn(goToThisPage, 'default').mockImplementation(() => console.log('hi'));
render(<Component />);
userEvent.click(screen.getByTestId("goToThisPage"));
expect(goToThisPageSpy).toHaveBeenCalled();
});
});
goToThisPage.js
export const goToThisPage = () => {
const url = "/url";
window.location.href = url;
};
export default goToThisPage;
Component.jsx
import React from "react";
import ChildComponent from "./ChildComponent";
import goToThisPage from "./goToThisPage";
export const Component = () => {
return <ChildComponent goToThisPage={goToThisPage} />
};
export default Component;
Example 2: (Not Recommend for React components!)
We can also get it working by calling the goToThisPage function via exports. This ensures the component is rendered with our spyOn and mockImplementation. To get this working for both browser and jest you need to ensure we run the original function if it's on browser. We can do this by creating a proxy function that determines which function to return based on a ENV that jest defines when it runs.
Component.jsx
import React from "react";
import ChildComponent from "./ChildComponent";
export const goToThisPage = () => {
const url = "/url";
window.location.href = url;
};
// jest worker id, if defined means that jest is running
const isRunningJest = !!process.env.JEST_WORKER_ID;
// proxies the function, if jest is running we return the function
// via exports, else return original function. This is because
// you cannot invoke exports functions in browser!
const proxyFunctionCaller = (fn) => isRunningJest ? exports[fn.name] : fn;
export const Component = () => {
return <ChildComponent goToThisPage={proxyFunctionCaller(goToThisPage)} />
};
export default Component;
Component.spec.jsx
import React from "react";
import { render, screen } from "#testing-library/react";
import userEvent from "#testing-library/user-event";
describe("goToThisPage", () => {
test("should call goToThisPage when button is clicked", async () => {
const Component = require('./Component');
const goToThisPageSpy = jest.spyOn(Component, 'goToThisPage').mockImplementation(() => console.log('hi'));
render(<Component.default />);
userEvent.click(screen.getByTestId("goToThisPage"));
expect(goToThisPageSpy).toHaveBeenCalled();
});
});
You can move the function proxy to it's own file but you need to pass exports into the proxy function as exports is scoped to it's own file.
Example code
// component.js
import React from "react";
import ChildComponent from "./ChildComponent";
import proxyFunctionCaller from "./utils/proxy-function-caller";
export const goToThisPage = () => {
const url = "/url";
window.location.href = url;
};
export const Component = () => {
return <ChildComponent goToThisPage={proxyFunctionCaller(typeof exports !== 'undefined' ? exports : undefined, goToThisPage)} />
};
export default Component;
// utils/proxy-function-caller.js
// jest worker id, if defined means that jest is running
const isRunningJest = !!process.env.JEST_WORKER_ID;
// proxies the function, if jest is running we return the function
// via exports, else return original function. This is because
// you cannot invoke exports functions in browser!
const proxyFunctionCaller = (exports, fn) => isRunningJest ? exports[fn.name] : fn;
export default proxyFunctionCaller;
There are other ways to do this but I would follow the first solution as you should be splitting utility functions into it's own files anyway. Goodluck.
Example 3 for #VinceN
You can mock a function that lives in the same file using the below example files.
SomeComponent.tsx
import * as React from 'react';
const someFunction = () => 'hello world';
const SomeComponent = () => {
return (
<div data-testid="innards">
{someFunction()}
</div>
)
}
export default SomeComponent;
SomeComponent.spec.tsx
import SomeComponent from './SomeComponent';
import { render, screen } from "#testing-library/react";
jest.mock('./SomeComponent', () => ({
__esModule: true,
...jest.requireActual('./SomeComponent'),
someFunction: jest.fn().mockReturnValue('mocked!')
}));
describe('<SomeComponent />', () => {
it('renders', () => {
render(<SomeComponent />);
const el = screen.getByTestId('innards');
expect(el.textContent).toEqual('mocked!');
});
});
You exporting both functions and then defining a default export of the Component itself is what's causing the problem (which is mixing up default and named exports).
Remove export default Component; and change the top import in your test file to import {Component, goToThisPage} from './component'. That said I'm not sure you even need to export goToThisPage (for the Jest test at least).

Test with component has been rendered

I am using Jest with Enzyme for unit testing. I have a component that renders components based on the media type. Under unit testing, I am checking if the appropriate component has been rendered.
My Component
const getComponent = {
'image': ImageComp,
'video': VideoComp,
'other': DocumentComp
}
const MediaDisplay = (props) => {
let { assetInfo } = props;
let source = assetInfo.assetUrl;
const PreviewComponent = getComponent[assetInfo.type];
return ( <div>
{source && <PreviewComponent assetInfo={assetInfo} />}
</div>
);
}
In unit testing,
import React from 'react';
import MediaDisplay from './../MediaDisplay';
import Enzyme, { mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() });
describe('<MediaDisplay/>', () => {
it('should render Image component when asset type is image', () => {
const mockAssetInfo = {
assetUrl:'https://example.com/image001.jpg',
type:'image'
};
const component = mount(<MediaDisplay assetInfo={mockAssetInfo} />);
expect(component).toMatchSnapshot();
});
});
I don't think I am writing the test case correctly. Can someone help me write this test case?
P.S - I have a separate test case inside the image component to check if image is rendered, where I am checking if the tag has length.
Thanks a ton in advance.
I think your test is not doing what is suppose to test, because your case is telling that it should render Image component when asset type is image but you're just checking that the component is matching the snapshot, if you're using mount you should be able to see the content of the child components, so for example if your PreviewComponent displays different things depending on the props passed you could test those behaviors. Or if you want to check just that the assetInfo.type is 'image' you can always do:
it("contains an assetInfo image type", () => {
expect(component.prop("assetInfo").type).toEqual(mockAssetInfo.type);
});
And if you only want to check that the child is present and renders successfully you could use shallow instead mount and do this:
import PreviewComponent from "your-component-route"
describe("renders a PreviewComponent", () => {
beforeAll(() => {
const mockAssetInfo = {
assetUrl:'https://example.com/image001.jpg',
type:'image'
};
const component = shallow(<MediaDisplay assetInfo={mockAssetInfo} />);
});
it("wraps a PreviewComponent component", () => {
expect(component.find(PreviewComponent).length).toBe(1);
});
});

React Jest/Enzyme Testing: useHistory Hook Breaks Test

I'm fairly new to React, so please forgive my ignorance. I have a component:
const Login: FunctionComponent = () => {
const history = useHistory();
//extra logic that probably not necessary at the moment
return (
<div>
<form action="">
...form stuff
</form>
</div>
)
}
When attempting to write jest/enzyme test, the one test case I've written is failing with the following error
` › encountered a declaration exception
TypeError: Cannot read property 'history' of undefined`
I've tried to use jest to mock useHistory like so:
jest.mock('react-router-dom', () => ({
useHistory: () => ({ push: jest.fn() })
}));
but this does nothing :( and I get the same error. Any help would be most appreciated
UPDATE:
So I figured it out. I was on the right path creating a mock for the useHistory() hook, I defined in the wrong place. Turns the mock needs to be define (at least for useHistory) outside of the scope of the test methods, ex:
import { shallow } from 'enzyme';
import React from 'react';
import Login from './app/componets/login.component';
jest.mock('react-router', () => ({
...jest.requireActual('react-router'),
useHistory: () => ({ push: jest.fn() })
}));
/**
* Test suite describing Login test
describe('<LoginPage>', () => {
test('should test something', () => {
//expect things to happen
});
})
With the above test runs without failing on history being undefined.
So I figured it out. I was on the right path by creating a mock for the useHistory() hook, I just defined it in the wrong place. Turns out the mock needs to be define (at least for useHistory) outside of the scope of the test methods, ex:
import { shallow } from 'enzyme';
import React from 'react';
import Login from './app/componets/login.component';
jest.mock('react-router', () => ({
...jest.requireActual('react-router'),
useHistory: () => ({ push: jest.fn() })
}));
/**
* Test suite describing Login test
*/
describe('<LoginPage>', () => {
test('should test something', () => {
//expect things to happen
});
})
With the above, the test runs without failing on history being undefined.

React.createElement: type is invalid -- expected a string warning in unit testing with enzyme

I am unit testing my react-native app. I have a login page which consist of a component and has following props passed from container component.
I am doing snapshot testing with jest and enzyme. I passed these props to Login Component like this
import Login from "../index";
import React from 'react';
import { shallow } from 'enzyme';
import globalStyles from '../../../../../global/styles';
let theme = 'DefaultTheme', wrapper;
const createTestProps = (props) => ({
navigation: {
navigate: jest.fn()
},
theme: theme,
globalStyles: globalStyles(theme),
session: {},
changeScene: jest.fn(),
changeTheme: jest.fn(),
setUser: jest.fn(),
logout: jest.fn(),
...props
});
beforeEach(() => {
const props = createTestProps({});
console.log(JSON.stringify(props));
wrapper = shallow(<Login {...props} />);
});
test('should render login component correctly', () => {
expect(wrapper).toMatchSnapshot();
});
Test passed and snapshot is created however this warning is shown in console.
Warning: React.createElement: type is invalid -- expected a string
(for built-in components) or a class/function (for composite
components) but got: object.
I check the export type it is default one.
export default Login
The global style object is a nested style object mainly consist of styles for button, text etc and it passed as a prop in every component of my app. Need help what I'm doing wrong.

React component test keeps failing

I keep getting this error when testing my TodoList component:
debug.html:38
Invariant Violation:
Element type is invalid:
expected a string (for built-in components) or a class/function (for composite components) but got: object.
I'm not sure what's causing it. Could anyone lead me into the right direction?
TodoList.js:
import React from 'react';
import Todo from 'Todo';
class TodoList extends React.Component {
renderTodos(todos) {
return todos.map(todo => {
return <Todo key={todo.id} {...todo} />;
});
}
render() {
const { todos } = this.props;
return (
<div>
{this.renderTodos(todos)}
</div>
);
}
}
export default TodoList;
TodoList.test.js:
const React = require('react');
const ReactDOM = require('react-dom');
const TestUtils = require('react-addons-test-utils');
const expect = require('expect');
const $ = require('jQuery');
const TodoList = require('TodoList');
const Todo = require('Todo');
describe('TodoList', () => {
it('should exist', () => {
expect(TodoList).toExist();
});
it('should render one Todo component for each todo item', () => {
const todos = [{
id: 1,
text: "Do something"
}, {
id: 2,
text: "Check mail"
}];
const todoList = TestUtils.renderIntoDocument(<TodoList todos={todos} />);
const todoComponents = TestUtils.scryRenderedComponentsWithType(todoList, Todo);
expect(todoComponents.length).toBe(todos.length);
});
});
Link to source code: link to source code
In your TodoList.Test.js, remove
const TodoList = require('TodoList');
const Todo = require('Todo');
and replace it with
import TodoList from 'TodoList'
import Todo from 'Todo
That is because you are exporting your components in the ES6 way and importing it the nodejs way.
If you still want to use require in your test then you will have to replace the
export default TodoList
with
module.exports = TodoList;
Hope it helps :)
alternatively:
const TodoList = require('TodoList').default;
const Todo = require('Todo').default;
By default in Node, if you use this syntax:
require('Todo')
It will look for the node module Todo, as in it will look in your node_modules directory for a directory named Todo. Node doesn't know how to use custom Webpack resolves. You have this setting:
modulesDirectories: [
'node_modules',
'./app/components'
],
Meaning any time Webpack sees a require('Todo') or import from 'Todo' it will check both in node_moudles for a file or folder named Todo, and it will check app/components for a folder.
If you want to use the same resolve trick in your tests, you have to build a test bundle with Webpack and run that bundle, the same way you build a browser bundle.
Otherwise you have to use the default Node way to require files not in node_modules, which is with relative paths:
const TodoList = require('../../components/TodoList');
const Todo = require('../../components/Todo');

Resources