As an example I used Make React PropType warnings throw errors with enzyme.js + sinon.js + mocha.js.
I have a React component with one required prop:
class Pagination extends Component {
render() {
return (
... render some stuff
);
}
}
Pagination.propTypes = {
total: PropTypes.number.isRequired
};
And this is test for it:
describe('(Component) Pagination', () => {
before(() => {
sinon.stub(console, 'error', (warning) => { throw new Error(warning) })
})
after(() => { console.error.restore() })
it('render fails without props', () => {
shallow(<Pagination />);
});
it('render fails without props2', () => {
shallow(<Pagination />);
});
});
After running that tests first one crashes, but second - not. Tests are similar.
I think that the problem is that React throws warning messages only once.
How to avoid this?
I want to have 2 tests: one that will be crashed when no props is set, and second works fine with props.
You can find a workaround here:
Pagination.displayName = Math.random().toString();
You were apparently right that it is because of the same component, and if you do this before each test, then it tricks it. Just a hack, but it works. Apparently no better way.
P.S. I did it in beforeEach to not write it in each test myself.
P.P.S. random is not most reliable, since you can get the same name and it will fail, a guid or whatever can be used if it's not good enough, it's just an example.
Related
I am currently trying to write a test to test what is inside of a higher order components
my test like so:
let Component = withEverything(Header);
let wrapper;
it('renders correctly', async () => {
wrapper = await mountWithSleep(
<Component componentProps={{ session: { id: '2' } }} />,
0.25
);
console.log(wrapper.debug());
});
});
outputs the following:
<Component>
<WithSession component={[Function: GlobalNav]} innerProps={{...}} />
</Component>
My with session file looks like the following:
import React, { Component, ComponentType } from 'react';
import { Session, session } from '#efa/web/src/modules/auth/authService';
import { Omit } from '#everlutionsk/helpers';
import { Subscription } from 'rxjs';
class WithSession extends Component<Props, State> {
state: State = {
session: undefined
};
private subscription: Subscription;
componentDidMount() {
this.subscription = session.subscribe(session => {
this.setState({ session });
});
}
componentWillUnmount() {
this.subscription.unsubscribe();
}
render() {
if (this.state.session === undefined) return null;
const Component = this.props.component;
const props = { ...this.props.innerProps, session: this.state.session };
return <Component {...props} />;
}
}
/**
* Injects a current session to the given [component].
*/
export function withSession<P extends SessionProps>(
component: ComponentType<P>
): ComponentType<Omit<P, keyof SessionProps>> {
return props => <WithSession component={component} innerProps={props} />;
}
export interface SessionProps {
readonly session: Session | null;
}
interface Props {
readonly component: ComponentType;
readonly innerProps: any;
}
interface State {
readonly session: Session | null | undefined;
}
I have tried to do a jest.mock which gets me part of the way using this:
jest.mock('#efa/web/src/modules/auth/components/withSession', () => {
//#ts-ignore
const original = jest.requireActual(
'#efa/web/src/modules/auth/components/withSession'
);
return {
__esModule: true,
...original,
withSession: component => {
return component;
}
};
});
Using this module i can at least see a returned component instead of with session. But now the issue is i need to be able to set the session state. wondering if anyone can help?!
It is worth noting this is a project which we inherited i would not of implemented it this way ever!
You're correct that this type of code absolutely makes testing more difficult. The "best" way in this case its probably a bit of work because the nicest way to go about it would be to switch this thing to context; and then add options to your test framework mount method to populate that context how you want from individual tests. Though, you can reach something similar with old fashioned HOCs.
There's a cheaper way, and that would be to allow options to be passed to withEverything (if this is used by the app as well, you can create a mirror one called withTestHocs or similiar. I.e.
withTestHocs(Header, {
session: //... object here
})
Internally, this HOC would no longer call withSession whatsoever. Instead, it would call a HOC who's only purpose is to inject that session config object into the component for test reasons.
There's no reason to do complex mocking to get the session right on every test, its a waste of time. You only need that if you're actually testing withSession itself. Here you should be prioritising your test framework API that makes having custom session per test nice and simple. jest.mock is not easily parametrised, so that in itself is also another good reason to not go down that road. Again, the exception is when you're unit testing the actual session hoc but those tests are typically quite edge-casey and wont be using the core "test framework HOC" you'll use for all your userland/feature code -- which is what I'm focusing on here.
Note with this solution, you wouldn't need the complex jest mocking anymore (provided all tests were moved to the new way).
export const withEverything =
(Component, {session}) =>
({ providerProps, componentProps }) =>
(
<MockedProvider {...providerProps}>
<BrowserRouter>
<FlashMessage>
<Component {...componentProps} session={session} />
</FlashMessage>
</BrowserRouter>
</MockedProvider>
);
Now in your test
it('renders correctly', async () => {
wrapper = await mountWithSleep(
const withEverything(Header, { session: { id: '2' }}),
0.25
);
console.log(wrapper.debug());
});
If you need to be able to manipulate the session mid-test, you could do that by returning a method from withEverthing that allows the session to be set, but im not sure if you need it.
I have a React JS component MyComponent and I would like to test the following use case:
It should call updateSomething() when component on mount
And I've come up with the following code:
System Under Test (SUT)
export class MyComponent extends React.Component<Props, State> {
public componentDidMount() {
console.log("componentDidMount"); // Debug purpose.
this.fetchSomething()
.then(() => {
console.log("fetchSomething"); // Debug purpose.
this.updateSomething();
});
}
// These are public for simplicity
public async fetchSomething(): Promise<any> { }
public updateSomething() {
console.log("updateSomething"); // Debug purpose.
}
}
Test
it("should update something, when on mount", () => {
const props = { ...baseProps };
sinon.stub(MyComponent.prototype, "fetchSomething").resolves();
const spy = sinon.spy(MyComponent.prototype, "updateSomething");
shallow(<MyComponent {...props} />);
sinon.assert.calledOnce(spy);
});
The result is the test failed with AssertError: expected updateSomething to be called once but was called 0 times but all three console.log() printed.
My understanding is since I want to test the event when on mount, I have to spy/stub it before it's even created, therefore I have to spy on MyComponent.Prototype. Also, for fetchSomething(), I have to stub the async call and make it .resolves() to let it progress.
But I couldn't understand how it can still console.log("updateSomething") without being spied.
I don't know about sinon and I don't know about ts, but with simple js and jest it'd be like this:
fetchSomething() = Promise.resolve();
Then, in your test, you wouldn't have to mock it and just use:
const spy = jest.spyOn(MyComponent.prototype, 'updateSomething');
To see if it was called:
expect(spy).toHaveBeenCalled();
According to the comments/answer from this post, the assertion comes before .updateSomething have been called. To solve this problem, I would've to await the componentDidMount lifecycle method.
So the fixed program is as below:
// SUT
public async componentDidMount() {
//...
return this.fetchSomething()
.then(() => {
//...
});
}
// Test
it("should update something, when on mount", () => {
const props = { ...baseProps };
// Disable lifecycle here to enable stub in between.
const wrapper = shallow(<MyComponent {...props} />, { disableLifecycleMethods: true });
sinon.stub(wrapper.instance(), "fetchSomething").resolves();
const stub = sinon.stub(wrapper.instance(), "updateSomething");
// Actually call component did mount.
wrapper.instance().componentDidMount().then(() => {
sinon.assert.calledOnce(stub);
});
});
I'm trying to get the hang of testing components in a React project. I have a single test file on a single component so far, and I'm trying to prepare this file as a test suite with multiple tests in it.
import React from 'react';
import Enzyme, { mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import HamburgerIcon from './HamburgerIcon';
Enzyme.configure({ adapter: new Adapter() });
describe('<HamburgerIcon />', () => {
const hamburgerIcon = mount(<HamburgerIcon showOverlay={showOverlay} />);
it('displays on mobile', () => {
...
...
});
it('has class .open after click', () => {
...
...
});
hamburgerIcon.unmount();
});
I've removed the guts of the two tests, but basically the two tests are wrapped inside of a describe function, and I'm trying to mount the component once and unmount the component once in an effort to keep things DRY (don't repeat yourself).
I've placed the mount before the two it functions, thinking that mounting the component before running tests makes logical sense.
I placed the unmount after the two test functions, which causes the error:
Method “simulate” is meant to be run on 1 node. 0 found instead.
I think this is happening because the component is unmounting before the tests are actually run.
If I mount and unmount in both tests, like this...
describe('<HamburgerIcon />', () => {
it('displays on mobile', () => {
const hamburgerIcon = mount(<HamburgerIcon showOverlay={showOverlay} />);
...
...
hamburgerIcon.unmount();
});
it('has class .open after click', () => {
const hamburgerIcon = mount(<HamburgerIcon showOverlay={showOverlay} />);
...
...
hamburgerIcon.unmount();
});
});
...the tests pass.
This seems excessive, though. What if my test suite has ten test functions it? Should I be mounting and unmounting like this for every single test?
You can use beforeEach and afterEach functions to set-up and clear your test.
afterEach(() => {
//do the unmounting and other stuff here
//this will be called after each test case
});
beforeEach(() => {
//do the mounting and setting up the test case here
//this will be called before each test case
});
Using Jest and Enzyme, how can I test if this.props.functionToTest was run?
class TestComponent extends Component {
static propTypes = {
functionToTest: PropTypes.func
}
componentDidMount() {
this.props.functionToTest()
}
}
In Jest, I've tried creating mockProps and passing them in when mounting the component.
let props = {
functionToTest = jest.fn(() => {});
}
beforeEach(() => {
const wrapper = mount(<TestComponent {...props} />
}
A console.log in the componentDidMount function shows functionToTest as undefined. Obviously passing in the props during mount isn't working.
Question 1: How can I pass in mock props that will show in the componentDidMount function?
Question 2: Once that function is available, how do I gain access to the function so I can use spyOn or something similar to test if the function was run?
I don't know your exact setup, but this is how I would do that:
Mock the function with jest.fn() like you did
Pass mock to the component being mounted (like apparently you did)
Check whether it was run with expect(...).toBeCalled() or .toHaveBeenCalled() (varies between different Jest versions)
.
let props = {
functionToTest: jest.fn() // You don't need to define the implementation if it's empty
};
beforeEach(() => {
const wrapper = mount(<TestComponent {...props} />
}
// In the test code:
it('does something', () => {
expect(props.functionToTest).toBeCalled();
// OR... depending on your version of Jest
expect(props.functionToTest).toHaveBeenCalled();
});
The problem ended up being that TestComponent was only being exported within the Redux wrapper. Adding an export at the class level and destructuring it in the Jest test import, along with the solution Henrick posted above fixed it.
I just finished writing my first Reactjs component and I am ready to write some tests (I used material-ui's Table and Toggle).
I read about jest and enzyme but I feel that I am still missing something.
My component looks like this (simplified):
export default class MyComponent extends Component {
constructor() {
super()
this.state = {
data: []
}
// bind methods to this
}
componentDidMount() {
this.initializeData()
}
initializeData() {
// fetch data from server and setStates
}
foo() {
// manuipulatig data
}
render() {
reutrn (
<Toggle
id="my-toggle"
...
onToggle={this.foo}
>
</Toggle>
<MyTable
id="my-table"
data={this.state.data}
...
>
</MyTable>
)
}
}
Now for the test. I want to write a test for the following scenario:
Feed initializeData with mocked data.
Toggle my-toggle
Assert data has changed (Should I assert data itself or it is better practice to assert my-table instead?)
So I started in the very beginning with:
describe('myTestCase', () => {
it('myFirstTest', () => {
const wrapper = shallow(<MyComponent/>);
}
})
I ran it, but it failed: ReferenceError: fetch is not defined
My first question is then, how do I mock initializeData to overcome the need of calling the real code that using fetch?
I followed this answer: https://stackoverflow.com/a/48082419/2022010 and came up with the following:
describe('myTestCase', () => {
it('myFirstTest', () => {
const spy = jest.spyOn(MyComponent.prototype, 'initializeData'
const wrapper = mount(<MyComponent/>);
}
})
But I am still getting the same error (I also tried it with componentDidMount instead of initializeData but it ended up the same).
Update: I was wrong. I do get a fetch is not defined error but this time it is coming from the Table component (which is a wrap for material-ui's Table). Now that I come to think about it I do have a lot of "fetches" along the way... I wonder how to take care of them then.
fetch is supported in the browser, but jest/enzyme run in a Node environment, so fetch isn't a globally available function in your test code. There are a few ways you can get around this:
1: Globally mock fetch - this is probably the simplest solution, but maybe not the cleanest.
global.fetch = jest.fn().mockResolvedValue({
json: () => /*Fake test data*/
// or mock a response with `.text()` etc if that's what
// your initializeData function uses
});
2: Abstract your fetch call into a service layer and inject that as a dependency - This will make your code more flexible (more boilerplate though), since you can hide fetch implementation behind whatever interface you choose. Then at any point in the future, if you decide to use a different fetch library, you can swap out the implementation in your service layer.
// fetchService.js
export const fetchData = (url) => {
// Simplified example, only takes 'url', doesn't
// handle errors or other params.
return fetch(url).then(res => res.json());
}
// MyComponent.js
import {fetchService} from './fetchService.js'
class MyComponent extends React.Component {
static defaultProps = {
// Pass in the imported fetchService by default. This
// way you won't have to pass it in manually in production
// but you have the option to pass it in for your tests
fetchService
}
...
initializeData() {
// Use the fetchService from props
this.props.fetchService.fetchData('some/url').then(data => {
this.setState({ data });
})
}
}
// MyComponent.jest.js
it('myFirstTest', () => {
const fetchData = jest.fn().mockResolvedValue(/*Fake test data*/);
const fetchService = { fetchData };
const wrapper = mount(<MyComponent fetchService={fetchService} />);
return Promise.resolve().then(() = {
// The mock fetch will be resolved by this point, so you can make
// expectations about your component post-initialization here
})
}