Relay fragment not returning mocked data in test - unit-testing

I'm trying to unit test this component (which uses a Relay JS fragment):
const Row = (me) => {
const {name, email} = useFragment(RowFragment, me)
return ...
}
const RowFragment = graphql`
fragment RowFragment_me on User {
email
name
}
`
To test it, I've wrapped it in a QueryRenderer with a mocked environment - similarly to how the docs suggest. Here is the contents on my test:
it('should mock response and render component', () => {
const environment = createMockEnvironment()
const TestComponent = props => {
return (
<Row me={props.viewer.me} />
)
}
const renderer = render(
<RelayEnvironmentProvider environment={environment}>
<QueryRenderer
environment={environment}
query={TestQuery}
render={({ props }) => {
if (props) return <TestComponent {...props} />
return <div>Loading...</div>
}}
variables={variables}
/>
</RelayEnvironmentProvider>
)
act(() => {
environment.mock.resolveMostRecentOperation(operation =>
MockPayloadGenerator.generate(operation)
)
})
}
The line const {name, email} = useFragment(RowFragment, me) now fails because the response from useFragment is undefined. I can see that me is correctly passed into the component.
Am I doing something wrong?

Related

How to test `dangerouslySetInnerHTML` attribute with Testing Library and Jest?

I have a component called Previewer :
export const createMarkup = (htmlStr: string) => {
return { __html: marked(htmlStr, markedConfig) };
};
export const Previewer = ({ content }: Props) => {
return (
<div
id='preview'
dangerouslySetInnerHTML={createMarkup(content)}
role='textbox'
></div>
);
};
I want to test if that div has an attribute called dangerouslySetInnerHTML and display the provided text:
test("should return a div with an id = preview", () => {
const defaultContent = "# Welcome to React Markdown";
render(<Previewer content={defaultContent} />);
const element = screen.getByRole("textbox");
expect(element).toHaveAttribute("id", "preview"); // passed
expect(element).toHaveAttribute("dangerouslySetInnerHTML", createMarkup(defaultContent)); // failed
});

Nextjs - getting cookies value on _app init

I need to get the cookies value on the first render. I get those in _app.tsx.
Everything looks fine (render correctly the html) but I get the server/client mismatch warning because at the first render on Server, cookies are undefined and the value fall back to default value which is 0.
On hydration, the value is picked from cookies and is displayed correctly.
Could someone explain to me why is a problem that on the server the value is the default value (therefor why I get this warning) and what would be a better way to write this code?
Here my _app.tsx
import React, { useState, useEffect } from 'react'
import type { AppProps } from 'next/app'
import { UserContext } from '../context/UserContext'
require('es6-promise').polyfill()
let cookieHelper
if (typeof window !== 'undefined') {
cookieHelper = require( '../helpers/_cookies' ) // This is a file written by us where we export const and get/set cookies func
}
function ElliotApp ({ Component, pageProps }: AppProps) {
useEffect(() => {
import('../helpers/_cookies')
}, [])
const searchesFromCookies = cookieHelper?.get(cookieHelper?.COOKIE_NAME_SEARCH_COUNT) // this value is a string like '3'
const userState = {
numOfSearches: searchesFromCookies || 0
}
const [userContext, setUserContext] = useState(userState)
useEffect(() => {
cookieHelper?.set(cookieHelper?.COOKIE_NAME_SEARCH_COUNT, userContext.numOfSearches)
}, [userContext])
return (
<UserContext.Provider value={[userContext, setUserContext]}>
<Component {...pageProps} />
</UserContext.Provider>
)
}
export default ElliotApp
many thanks!
Could someone explain to me why is a problem that on the server the value is the default value
Probably because your cookieHelper is just reading cookies from document.cookie and there is no such thing on the server.
If you want to get cookie with SSR you could use getInitialProps:
function parseCookies(req) {
// cookie.parse is some function that accepts cookie string and return parsed object
return cookie.parse(req ? req.headers.cookie || "" : document.cookie)
}
ElliotApp.getInitialProps = async ({ req }) => {
const cookies = parseCookies(req)
return {
searchesFromCookies: cookies[COOKIE_NAME_SEARCH_COUNT]
}
}
and then do something with them in your App component:
function ElliotApp ({ Component, pageProps, searchesFromCookies }: AppProps) {
const userState = {
numOfSearches: searchesFromCookies || 0
}
const [userContext, setUserContext] = useState(userState)
// do whatever ...
}
EDIT:
In case you are fine with default value on the server then you just need to do everything inside useEffect hook (it wont run on the server):
function ElliotApp ({ Component, pageProps }: AppProps) {
const userState = {
numOfSearches: 0
}
const [userContext, setUserContext] = useState(userState)
useEffect(() => {
setUserContext({
numOfSearches: cookieHelper.get(cookieHelper.COOKIE_NAME_SEARCH_COUNT)
});
}, []);
useEffect(() => {
cookieHelper.set(cookieHelper.COOKIE_NAME_SEARCH_COUNT, userContext.numOfSearches)
}, [userContext])
return (
<UserContext.Provider value={[userContext, setUserContext]}>
<Component {...pageProps} />
</UserContext.Provider>
)
}

Testing redux connected component

I have the following connected component in React-Redux
export class IncrementalSearch extends React.Component {
constructor(props) {
super(props);
this.onSearch$ = new Subject();
this.onChange = this.onChange.bind(this);
}
componentDidMount() {
this.subscription = this.onSearch$
.debounceTime(300)
.subscribe(debounced => {
this.props.onPerformIncrementalSearch(debounced);
});
}
componentWillUnmount() {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
onChange(e) {
const newText = e.target.value;
this.onSearch$.next(newText);
}
render() {
return (
<div className={styles.srchBoxContaner}>
<input
className={styles.incSrchTextBox}
type="text" name="search" id="searchInput" placeholder="Search.."
onChange={this.onChange}
/>
</div>
);
}
}
const mapDispatchToProps = (dispatch) => ({
onPerformIncrementalSearch: (searchText) => {
dispatch(performIncrementalStoreSearch(searchText));
}
});
const IncrementalSearchComponent = connect(null, mapDispatchToProps)(IncrementalSearch);
export default IncrementalSearchComponent;
I'm now trying to write a unit tests for the connected component. I'm using Jest, Enzyme, and Sinon. So far this is what my unit test looks like
it('calls \'onPerformIncrementalSearch\' when the user types in something', () => {
const mockStore = configureStore();
const onPerformIncrementalSearchSpy = sinon.spy();
const mapStateToProps = null;
const mapDispatchToProps = {
onPerformIncrementalSearch: onPerformIncrementalSearchSpy
};
const mappedProps = { mapStateToProps, mapDispatchToProps };
const incrementalSearchWrapper =
mount(
<Provider store={mockStore}>
<IncrementalSearchComponent
onPerformIncrementalSearch={onPerformIncrementalSearchSpy}
props={mappedProps}
store={mockStore}
/>
</Provider>
);
//find the input element
const searchInput = incrementalSearchWrapper.find('#searchInput');
searchInput.node.value = 'David';
searchInput.simulate('change', searchInput);
expect(onPerformIncrementalSearchSpy.called).toEqual(true);
// onChangeSpy.restore();
});
However, when I run this test, I get the following error
TypeError: Cannot read property 'bind' of undefined
How do I fix this?
Testing connected components can be a huge pain. I find that it's more trouble than it's worth to try to wrap your components with a Provider to give them access to the store.
Instead, I would just export the component, mapStateToProps, and mapDispatchToProps and test them individually. Your app will still work the same if you export the connected component as the default.
Dan Abramov (Co author of Redux) suggests this approach in this comment
I would also suggest looking into enzyme shallow rendering instead of using mount when testing connected components.

How to stop redux-form or React from changing htmlFor and id when creating Jest snapshots?

I've got a wizard form made with redux-forms v6 and it looks something like:
--
index.js - Holds page number in local state, is connected to application level state
PageOne - wrapped with reduxForm decorator (form: 'wizForm')
PageTwo - wrapped with reduxForm decorator (form: 'wizForm')
--
PageOne and PageTwo both contain additional components that render sections of the form (initial fields, vehicle information, driver information...), and each of those sections render their own components for each question in that section.
Since there's a lot of nested components and I want to test that PageOne and PageTwo call the props passed from index.js, I've resorted to using Enzyme's mount() function with a fake store. I want to MatchSnapshot() with Jest to compare whether index.js is rendering PageOne or PageTwo, after certain buttons are clicked to go back and forth from pages.
The problem is when I do create snapshots, other than creating a 16,000 line snapshot, the snapshot will NEVER match the previous one even if I don't change anything. I'm not sure if it's redux-form that's doing it or React, but the htmlFor and the id keep changing between snapshots, test after test after test.
We use css-modules too, but I don't think that's causing the problem, and we did configure Jest to work with css-modules too, modifying "moduleNameWrapper" to mock .css files. Does anyone know how to fix this or where I should look?
tests:
describe('<VehicleAddition />', () => {
let props;
beforeEach(() => {
props = {
...,
};
});
it('Renders initially', () => {
const component = shallow(<VehicleAddition {...props} />);
expect(toJson(component)).toMatchSnapshot();
});
it('Renders <PageTwo> when <PageOne> form is submitted', () => {
const component = shallow(<VehicleAddition {...props} />);
expect(toJson(component)).toMatchSnapshot();
component.find('ReduxForm') // reduxForm HOC wraps the <form> in a <ReduxForm> component
.first()
.simulate('submit');
expect(toJson(component)).toMatchSnapshot();
expect(component.state().page).toEqual(2);
});
it('PageTwoStuffs', () => {
// Render the form, click 'next', assert it's page two
// click 'previous'
jest.enableAutomock();
const store = createStore(
combineReducers({
route: jest.fn(() => Immutable.fromJS({})),
language: jest.fn(() => Immutable.fromJS({})),
global: jest.fn(() => Immutable.fromJS({})),
form: formReducer,
}),
Immutable.fromJS({}),
);
const component = mount(
<Provider store={store}>
<VehicleAddition {...props} />
</Provider>
);
// CAN'T check the state of <VehicleAddition /> because it can only be done on root component, says the error message.
expect(toJson(component)).toMatchSnapshot();
index.js:
export class VehicleAddition extends React.Component { // eslint-disable-line
constructor(props) {
super(props);
this.state = {
page: 1,
};
}
nextPage = () => {
this.setState({ page: this.state.page + 1 });
}
previousPage = () => {
this.setState({ page: this.state.page - 1 });
}
render() {
return (
<div>
{page === 1 &&
<PageOne
{...this.props}
/>
}
{page === 2 &&
<PageTwo
{...this.props}
/>
}
</div>
);
}
}
PageOne.js
class PageOne extends React.Component { // eslint-disable-line
render() {
const {
...
} = this.props;
return (
<form onSubmit={handleSubmit}>
<div>
<InitialFields
autoPolicies={autoPolicies}
changeField={this.changeField}
getFormValues={getFormValues}
policies={policies}
primary={primary}
/>
<VehicleBeingAddedFields
changeField={this.changeField}
getFormValues={getFormValues}
fetchVehMakes={fetchVehMakes}
fetchVehModels={fetchVehModels}
policies={policies}
vehMakes={vehMakes}
vehModels={vehModels}
/>
...
<div className="btn-group btn-group-float-right">
<button
type="submit"
onClick={this.handleClick}
disabled={pristine || submitting}
className="btn-primary"
>
Next
</button>
</div>
</form>
);
}
}
PageTwo.js:
class PageTwo extends React.Component { // eslint-disable-line
render() {
const {
...
} = this.props;
return (
<form onSubmit={handleSubmit}>
...
<div className="btn-group btn-group-float-right">
<button type="button" className="btn" onClick={previousPage}>Previous</button>{' '}
<button type="submit" disabled={pristine || submitting} className="btn-primary">Submit</button>
</div>
</form>
);
}
}
Example of the parts of the snapshot that constantly changes:
I solved it by passing a hardcoded id value from the test cases
import React from 'react';
import renderer from 'react-test-renderer';
import { reduxForm } from 'redux-form';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import { mount } from 'enzyme'
import TodoItem from './TodoItem';
import injectTapEventPlugin from 'react-tap-event-plugin';
function setup() {
const spy = jest.fn();
const store = createStore(() => ({}));
const Decorated = reduxForm({ form: 'testForm' })(TodoItem);
const props = {
remove: jest.fn(),
TodoItemReduxFormInitialName: "fullName",
snapshotTestId:"4"
}
const mockedComponent = <Provider store={store}>
<Decorated {...props} />
</Provider>;
const enzymeWrapper = mount(mockedComponent)
injectTapEventPlugin();
return {
props,
mockedComponent,
enzymeWrapper
}
}
describe('TodoItem Component', () => {
it('should render the snapshot', () => {
const {mockedComponent} = setup()
const tree = renderer.create(
mockedComponent
).toJSON();
expect(tree).toMatchSnapshot();
});
//not required as snapshot testing covers it
it('should render Number', () => {
const {enzymeWrapper} = setup()
const fieldProps = enzymeWrapper.find('Field').at(0).props();
expect(fieldProps.hintText).toEqual('Item Number');
expect(fieldProps.name).toEqual('fullName.itemNumber');
});
//not required as snapshot testing covers it
it('should render remove button', () => {
const {enzymeWrapper} = setup()
const button = enzymeWrapper.find('RaisedButton').at(0).props();
expect(button.label).toEqual("remove")
});
});

Enzyme: How to test onSubmit function passed as prop?

I am fairly new with enzyme. I have two components under test.
form.jsx
const LoginForm = ({ style, handleSubmit }) => {
return (
<form onSubmit={handleSubmit}>
<Button type='submit'>
Login
</Button>
</form>
);
};
LoginForm.propTypes = {
handleSubmit: PropTypes.func.isRequired
};
I am using this component in another component as follows:
Component.jsx
export default class Login extends Component {
constructor(props) {
super(props);
this.onLogin = this.onLogin.bind(this);
}
onLogin(event) {
event.preventDefault();
this.props.loginUser();
}
render() {
return (
<LoginForm style={loginFormStyles} handleSubmit={this.onLogin} />
);
}
}
Login.propTypes = {
auth: PropTypes.object.isRequired, //mapStateToProps
loginUser: PropTypes.func.isRequired //mapDispatchToProps
};
I have written tests for form and they are passing.
form-test.js
it('should have a onSubmit handler', () => {
const props = {
handleSubmit: () => {}
};
const wrapper = shallow(<LoginForm {...props} />);
expect(_.isFunction(wrapper.props().onSubmit)).to.be.true;
});
it('should should call handlesubmit on form submission', () => {
const handleSubmit = sinon.spy();
const wrapper = shallow(<LoginForm handleSubmit={handleSubmit} />);
wrapper.simulate('submit');
expect(handleSubmit).to.have.been.called;
});
These tests are passing. The confusing part is:
1- How do I test onLogin function in Component.jsx from form.jsx?
2- Vice versa, if I have to trigger onSubmit of form.jsx from component.jsx how would I do that?
First of all, you can rename the Component.jsx to something else.
And for the test you can do something as below,
import Component from '../src/login';
import { stub } from 'sinon';
describe('login', () => {
it('should call onsubmit', () => {
const onSubmit = stub()
.withArgs('username', 'password');
const loginComponent = mount(<LoginForm handleSubmit={onSubmit} /> );
loginComponent.simulate('submit');
expect(onSubmit.calledOnce).to.equal(true);
});
});
I have not tested this but it is close to what you are looking at.
Update:
I tested this and it is working.