How to mock Picker and Picker.Item with jest in React-Native? - unit-testing

I am trying to snapshot test this snippet of code:
import React, { Component } from 'react';
import {
Picker,
} from 'react-native';
export default class TestComponent extends Component {
render() {
return (
<Picker
selectedValue={this.props.asset.type}
onValueChange={this.props.onTypeChange}>
<Picker.Item label="Type of asset" value="default" />
<Picker.Item label="Car" value="car" />
<Picker.Item label="Boat" value="boat" />
<Picker.Item label="Ship" value="ship" />
</Picker>
);
}
}
My test looks like this right now:
import 'react-native';
import React from 'react';
import TestComponent from './TestComponent';
import renderer from 'react-test-renderer';
describe('TestComponent', () => {
const asset = {
type: 'car',
}
it('renders correctly', () => {
const tree = renderer.create(
<TestComponent
asset={asset} />
).toJSON();
expect(tree).toMatchSnapshot();
});
})
My problem is that I get:
TypeError: Cannot read property '_tag' of undefined
I think that I should mock it based on this issue
I have tried adding simply:
jest.mock('Picker', () => 'Picker')
But than it still throws an error because Picker.Item is still not mocked
Invariant Violation: Element type is invalid: expected a string (for built-
in components) or a class/function (for composite components)
but got: undefined. Check the render method of `TestComponent`.
Other variants I tried with no avail:
jest.mock('Picker', () => {return {Item: 'Item'}});
----------------------------------------------------
class Picker{
Item = 'PickerItem'
}
jest.mock('Picker', () => {
return Picker;
});

Created a github issue as well and here is a working answer:
jest.mock('Picker', () => {
const Picker = class extends Component {
static Item = props => React.createElement('Item', props, props.children);
static propTypes = { children: React.PropTypes.any };
render() {
return React.createElement('Picker', this.props, this.props.children);
}
}
return Picker;
})

For Expo v39, I was able to test #react-native-community/picker by adding the following mock to my test/setup file:
jest.mock('#react-native-community/picker', () => {
const React = require('React')
const RealComponent = jest.requireActual('#react-native-community/picker')
class Picker extends React.Component {
static Item = (props: { children: never }) => {
return React.createElement('Item', props, props.children)
}
render () {
return React.createElement('Picker', this.props, this.props.children)
}
}
Picker.propTypes = RealComponent.propTypes
return {
Picker
}
})
Note that #react-native-community/picker is now react-native-picker/picker.
https://jestjs.io/docs/en/tutorial-react-native#tips

Related

Unit testing Vue component that fetches data and sends to component as prop

This Vue3 component contains a service that fetches an array of workshops from the database and passes this array as a prop to its child component(s). I want to make sure that the data is the expected data and in the expected form. How might I test this? If I mock this data, I wouldn't get that assurance. Here is my component WorkshopsView:
<script setup lang="ts">
import { useGetWorkshops } from '#/services/useGetWorkshops'
import WorkshopsGrid from '#/components/WorkshopsGrid.vue'
const { data: workshops, error: workshopsError } = useGetWorkshops()
</script>
<template>
<div>
<WorkshopsGrid :workshops="workshops" :workshopsError="workshopsError" />
</div>
</template>
Here is the service useGetWorkshops:
import axios from 'axios'
import useSWRV from 'swrv'
const useGetWorkshops = () =>
useSWRV('workshops', async () => {
const response = await axios.get(`/api/workshops`)
if (response.data.error) {
return null
}
return response.data.data
})
export default useGetWorkshops
Here is my Jest test file for WorkshopsView so far:
import { shallowMount } from '#vue/test-utils'
import WorkshopsView from '#/components/WorkshopsView.vue'
import WorkshopsGrid from '#/components/WorkshopsGrid.vue'
import useGetWorkshops from '#/services/useGetWorkshops'
jest.mock('#/services/useGetWorkshops', () => {
return jest.fn().mockImplementation(() => {
return { data: [], error: null }
})
})
describe('WorkshopsView', () => {
it('calls the child component', () => {
const wrapper = shallowMount(WorkshopsView)
expect(wrapper.findComponent(WorkshopsGrid).exists()).toBe(true)
})
it('calls the service to fetch the data', () => {
const wrapper = shallowMount(WorkshopsView)
expect(useGetWorkshops).toHaveBeenCalled()
}
}
How might it be tested that the component is getting the right data in the expected form to pass to the child component as a prop?

vuejs unit test a component which has a child component

I want to test a TheLogin.vue component that has a child BaseInput.vue component. I tried the code below and also shallowMount but I keep getting the error below.
TheLogin.vue
<template>
<section>
<legend>
Hello Login
</legend>
<BaseInput id="userName"></BaseInput>
</section>
</template>
export default {
name: 'TheLogin',
data() {
return {
userName: null
}
}
}
TheLogin.spec.js
import TheLogin from '#/pages/login/TheLogin.vue';
import BaseInput from '#/components/ui/BaseInput.vue';
import { createLocalVue, mount } from '#vue/test-utils';
describe('TheLogin.vue', () => {
const localVue = createLocalVue();
localVue.use(BaseInput); // no luck
it('renders the title', () => {
const wrapper = mount(TheLogin, {
localVue,
// stubs: {BaseInput: true // no luck either
// stubs: ['base-input'] // no luck again
});
expect(wrapper.find('legend').text()).toEqual(
'Hello Login'
);
});
I import my base components in a separate file which I import into my main.js
import Vue from 'vue';
const components = {
BaseInput: () => import('#/components/ui/BaseInput.vue'),
BaseButton: () => import('#/components/ui/BaseButton.vue'),
//et cetera
};
Object.entries(components).forEach(([name, component]) =>
Vue.component(name, component)
);
The error I'm getting is:
TypeError: Cannot read property 'userName' of undefined
UPDATE
Turned out it was Vuelidate causing the error (the code above was not complete). I also had in my script:
validations: {
userName: {
required,
minLength: minLength(4)
},
password: {
required,
minLength: minLength(4)
}
}
I solved it by adding in my test:
import Vuelidate from 'vuelidate';
import Vue from 'vue';
Vue.use(Vuelidate);
Have you tried to shallow mount the component without using localVue and setting BaseInput as a stub?
Something like:
import TheLogin from '#/pages/login/TheLogin.vue';
import { shallowMount } from '#vue/test-utils';
describe('TheLogin.vue', () => {
it('renders the title', () => {
const wrapper = shallowMount(TheLogin, {
stubs: { BaseInput: true }
});
expect(wrapper.find('legend').text()).toEqual(
'Hello Login'
);
});
});

How to test events on the root element of component in vue 2?

I'm writing unit tests for the following component:
<template>
<sub-component
#foo="bar"
/>
</template>
<script>
import SubComponent from './SubComponent';
export default {
name: 'MyComponent',
components: { SubComponent },
methods: {
bar(payload) {
this.$emit('baz', ...payload);
}
}
}
</script>
And the test would be:
import { shallowMount } from '#vue/test-utils';
import _ from 'lodash';
import MyComponent from '../../components/MyComponent';
describe('MyComponent.vue', () => {
let wrapper;
beforeEach(() => {
wrapper = shallowMount(MyComponent);
});
it('should emit baz on subcomponent foo', () => {
const subComp = wrapper.find('sub-component-stub');
expect(subComp.exists()).toBe(true); // passes
subComp.vm.$emit('foo');
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.emitted().baz).toBeTruthy(); // does not pass;
// upon logging:
console.log(_.isEqual(wrapper, subComp)); // => true
})
})
})
The example is oversimplified, but the principle here is I want a reusable <sub-component> (a modal) and various functional wrappers around it (related to one particular task the modal type performs) which map additional functionality. I don't want the functionality in the parent components, as it would violate DRY - i'd have to place it in each component containing a particular type of modal.
This would work fine if <sub-component> was not the direct child of <template>. Somehow, it appears wrapper and subComp are hosted on the same element.
How should this be tested properly?
Another possibility it's to find your element in the dom and check the emitted value of your root component.
import { shallowMount } from '#vue/test-utils'
import MyComponent from './MyComponent.vue'
import SubComponent from './SubComponent.vue'
describe('MyComponent', () => {
it('should emit baz on subcomponent foo', () => {
const wrapper = shallowMount(MyComponent)
const subComponent = wrapper.find(SubComponent)
expect(subComponent.exists()).toBe(true)
expect(wrapper.emitted('baz')).toBeUndefined()
subComponent.vm.$emit('foo', ['hello'])
expect(wrapper.emitted('baz')[0]).toEqual(['hello'])
// or expect(wrapper).toEmit('baz', 'hello') cf. below for toEmit
})
})
If you want a custom matcher for Jest:
toEmit(received, eventName, data) {
if (data) {
expect(received.emitted()[eventName][0]).toEqual([data])
} else {
expect(received.emitted()[eventName][0]).toEqual([])
}
return { pass: true }
}

How to properly unit test login and local storage

After 3 days researching and not ariving anywhere, I decided to ask here for someone that already have similar experience or can point a better path to follow.
The better SO question I've found was this but left some questions in air: React - how to test form submit?
Since I'm begginer I believe I may getting something wrong, but no sure exactly which. If it's the way I build the components or even test concept itself.
I have the following case:
When a user logins in, it calls API (mock) then save token result (when successful) to localStorage (mock)
When user is already logged in, it gets redirected to homepage
My code until now:
Login Component
class Login extends React.Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: ''
};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleSubmit(e) {
e.preventDefault();
this.props.sendLoginRequest(this.state).then(
({data}) => {
console.log(data);
},
(data) => {
console.error(data);
}
);
}
handleChange(e) {
this.setState({ [e.target.name]: e.target.value });
}
render() {
return (
<div id='auth-container' className='login'>
<Form onSubmit={this.handleSubmit}>
<FormGroup controlId='emailaddress'>
<InputGroup bsSize='large'>
<InputGroup.Addon>
<Icon glyph='icon-fontello-mail' />
</InputGroup.Addon>
<FormControl
autoFocus
className='border-focus-blue'
type='email'
placeholder='email#fixdin.com'
name='email'
onChange={this.handleChange}
value={this.state.email} />
</InputGroup>
</FormGroup>
<FormGroup controlId='password'>
<InputGroup bsSize='large'>
<InputGroup.Addon>
<Icon glyph='icon-fontello-key' />
</InputGroup.Addon>
<FormControl
className='border-focus-blue'
type='password'
placeholder='password'
name='password'
onChange={this.handleChange}
value={this.state.password} />
</InputGroup>
</FormGroup>
</Form>
</div>
)
}
}
Login.propTypes = {
sendLoginRequest: React.PropTypes.func.isRequired
}
authAction.js
import createApi from '../services/api';
import { saveToken } from '../services/session';
export function sendLoginRequest(loginData) {
return dispatch => {
const api = createApi();
const loginPromise = api.post('auth/', loginData);
loginPromise.then(
({ data }) => {
saveToken(data.token);
}
);
return loginPromise;
}
}
API..js
import axios from 'axios';
import { isAuthenticated, getToken } from './session';
export const BASE_URL = 'http://localhost:8000/api/v1/';
export default function createAPI() {
let auth = { }
if (isAuthenticated()) {
auth = {
Token: getToken()
}
}
return axios.create({
baseURL: BASE_URL,
auth: auth
});
};
session.js
const TOKEN_KEY = 'token';
export function saveToken(value)
{
localStorage.setItem(TOKEN_KEY, value);
}
export function getToken()
{
return localStorage.getItem(TOKEN_KEY)
}
export function isAuthenticated() {
return getToken() !== null;
}
My test stack is Mocha/Chai/Enzyme/sinon and it's defined
setup.js
var jsdom = require('jsdom');
class LocalStorageMock {
constructor() {
this.store = {};
}
clear() {
this.store = {};
}
getItem(key) {
return this.store[key];
}
setItem(key, value) {
this.store[key] = value.toString();
}
};
if(!global.document) {
global.document = jsdom.jsdom('<!doctype html><html><body></body></html>');
global.window = document.defaultView;
global.navigator = {userAgent: 'node.js'};
global.localStorage = new LocalStorageMock;
}
login-test.js
import React from 'react';
import sinon from 'sinon';
import { mount, shallow } from 'enzyme';
import { expect } from 'chai';
import { Provider } from 'react-redux';
import axios from 'axios'
import moxios from 'moxios'
import store from './../src/store';
import LoginPage from './../src/auth/components/Login';
describe('Login', () => {
beforeEach(function () {
moxios.install(axios)
})
afterEach(function () {
moxios.uninstall(axios)
})
it('should call action on form submit', () => {
const submitRequest = sinon.stub(LoginPage.prototype, 'handleSubmit').returns(true);
const wrapper = mount(<Provider store={store}><LoginPage /></Provider>);
wrapper.find('form').simulate('submit');
expect(submitRequest.called).to.be.true;
submitRequest.restore();
});
it('should save token on succesfull login', () => {
const wrapper = mount(<Provider store={store}><LoginPage /></Provider>);
const emailInput = wrapper.find('input[type="email"]');
const passInput = wrapper.find('input[type="password"]');
const form = wrapper.find('form');
emailInput.value = "valid#email.com";
passInput.value = '123456789';
form.simulate('submit'); // Should I use submit button instead???
moxios.wait(function () {
let request = moxios.requests.mostRecent()
request.respondWith({
status: 200,
response:
{ Token: 'validToken' }
}).then(function () {
expect(localStorage.getItem('Token')).to.equal('validToken');
});
});
});
});
Above test does not pass, since it returns false for submitRequest.called and second test fails with error "Cannot read property 'respondWith' of undefined". I'm not sure how to fix and more, I'm not sure if I idealized it right!!
When doing a lot of research about it, I've seen examples with tests specific for component method call + isolated action test.
So...
When I think about "click login and save token" I'm overthinking a unit test? There's a better way to test things like that? Maybe separate some concerns?
This is the correctly way to test if a form submit invoke its callback? If so, why sinon is not working there?
This is the correctly way to mock + test api call to login and localStorage? If so, why Moxios is not working properly? It keeps giving me that mostRecent() is undefined.
If no, to question 2 and 3, where can I find a valid and working example of how to properly test cited behavior?
Thanks in advance.

How to unit test a method of react component?

I am trying to unit test my reactjs component:
import React from 'react';
import Modal from 'react-modal';
import store from '../../../store'
import lodash from 'lodash'
export class AddToOrder extends React.Component {
constructor(props) {
super(props);
this.state = {checked: false}
//debugger
}
checkBoxChecked() {
return true
}
render() {
console.log('testing=this.props.id',this.props.id )
return (
<div className="order">
<label>
<input
id={this.props.parent}
checked={this.checkBoxChecked()}
onChange={this.addToOrder.bind(this, this.props)}
type="checkbox"/>
Add to order
</label>
</div>
)
}
}
export default AddToOrder;
Just to get started I am already struggling to assert the checkBoxChecked method:
import React from 'react-native';
import {shallow} from 'enzyme';
import {AddToOrder} from '../app/components/buttons/addtoorder/addtoorder';
import {expect} from 'chai';
import {mount} from 'enzyme';
import jsdom from 'jsdom';
const doc = jsdom.jsdom('<!doctype html><html><body></body></html>')
global.document = doc
global.window = doc.defaultView
let props;
beforeEach(() => {
props = {
cart: {
items: [{
id: 100,
price: 2000,
name:'Docs'
}]
}
};
});
describe('AddToOrder component', () => {
it('should be handling checkboxChecked', () => {
const wrapper = shallow(<AddToOrder {...props.cart} />);
expect(wrapper.checkBoxChecked()).equals(true); //error appears here
});
});
```
How can I unit test a method on the component? This is the error I am getting:
TypeError: Cannot read property 'checked' of undefined
You are almost there. Just change your expect to this:
expect(wrapper.instance().checkBoxChecked()).equals(true);
You can go through this link to know more about testing component methods using enzyme
For those who find the accepted answer as not working, try using .dive() on your shallow wrapper before using .instance():
expect(wrapper.dive().instance().somePrivateMethod()).toEqual(true);
Reference: Testing component methods with enzyme
Extend of previous answer.
If you have connected component (Redux) , try next code :
const store=configureStore();
const context = { store };
const wrapper = shallow(
<MyComponent,
{ context },
);
const inst = wrapper.dive().instance();
inst.myCustomMethod('hello');