Jest test validate data prop - javascript

I am new to jest and wanted to learn basics of how the test is written. How to write a simple test that validates uri contains/returns value?
renderProfileImage () {
const apiUrl = 'site.com'
const profileImagePath = this.props.data.field_profile_image
if (profileImagePath !== '') {
return <Image
style={styles.profile}
source={this.state.imageLoading ? require('../img/profileImagePlaceholder.png') : { uri: `${apiUrl}${profileImagePath}` }}
onLoadEnd={(e) => this.setState({ imageLoading: false })}
/>
}
say this.props.data.field_profile_image returns /photo.png

Remember that "React elements are plain objects":
import * as React from 'react';
import renderer from 'react-test-renderer';
import { MyComponent } from './path-to-your-component';
describe('renderProfileImage', () => {
it('should set the correct uri', () => {
const comp = renderer.create(<MyComponent data={{
field_profile_image: '/path-to-the-image'
}}/>).root.instance;
// element is just an object representing an <Image>...
const element = comp.renderProfileImage();
// ...so check that element.props.source was set correctly
expect(element.props.source).toEqual({ uri: 'site.com/path-to-the-image' });
});
});

Related

Jest test onClick for util function

Hey guys I am facing issue while trying to add test case for onClick in MetaDetails.tsx file
utils.js
export const handlePrintLabelButtonClick = (
e,
rmaNumber,
labelUrl,
getReturnLabel
) => {
const rmaList = [];
e.preventDefault();
if (!labelUrl) {
// some logic
} else {
// some logic
}
};
PrintLabel.tsx
import { DefaultButton } from "some path";
import { AnchorWrapper, ButtonWrapper } from "./index.styles";
export const PrintLabelButton = ({
target,
url,
type,
text,
onClickHandle
}: PrintLabelButtonProps) => {
return (
<ButtonWrapper>
<AnchorWrapper
href={url}
target={target}
type={type}
>
<DefaultButton
tabIndex="0"
onClick={onClickHandle}
data-test="print-label-button"
>
{text}
</DefaultButton>
</AnchorWrapper>
</ButtonWrapper>
);
};
MetaDetails.tsx
// Some lines of code
import { PrintLabelButton } from "./printLabel";
import { handlePrintLabelButtonClick } from "utils";
export const OrderMetaDetails = () => {
// some logic
return(
//Some React code
{showPrintLabelButton && (
<PrintLabelButton
onClickHandle={e =>
handlePrintLabelButtonClick(e, rmaNumber, labelUrl, getLabel)
}
url={labelUrl}
target="_blank"
type="printLabel"
text={intl.formatMessage(messages.printLabelText)}
/>
)}
// Some React code
)
}
What I've tried
MetaDetails.test.tsx
test("Order Meta Details Print Label Click", () => {
const handlePrintLabelButtonClick = jest.fn();
const wrapper = mountWithIntl(
<OrderMetaDetails
getLabel={() => {}}
info={/*some data*/}
intl={/*intl*/ }
/>
);
const component = wrapper.find(`[data-test="print-label-button"]`).hostNodes();
component.simulate("click")
expect(handlePrintLabelButtonClick).toHaveBeenCalled();
});
Jest throws the following error
Error: expect(jest.fn()).toHaveBeenCalled()
Expected number of calls: >= 1
Received number of calls: 0
When running the test coverage, in the function coverage I see that the "handlePrintLabelButtonClick" in MetaDetails.tsx is shown as uncovered. I have tried other approaches as well but none worked. I am new to unit testing. Can someone please guide as to what is the correct approach.
Note 1: I am using react/#emotion
Note 2: I have commented or written text such as "some logic" since there are lots of unwanted lines.
Your source code still uses the original handlePrintLabelButtonClick function, not the mock function that you created in the test.
You need to mock the actual exported function, for example using jest.mock:
import { handlePrintLabelButtonClick } from './utils.js';
const mockHandlePrintLabelButtonClick = jest.fn();
// Mock the module.
jest.mock('./utils.js', () => {
handlePrintLabelButtonClick: mockHandlePrintLabelButtonClick
}
describe('something', () => {
test("Order Meta Details Print Label Click", () => {
const wrapper = mountWithIntl(
<OrderMetaDetails
getLabel={() => {}}
info={/*some data*/}
intl={/*intl*/ }
/>
);
const component = wrapper.find(`[data-test="print-label-button"]`).hostNodes();
component.simulate("click")
expect(mockHandlePrintLabelButtonClick).toHaveBeenCalled();
});
})

Using React Context with AsyncStorage using hooks

My goal is to use custom hooks created from Context to pass and modify stored values
The final goal is to use something like useFeedContext() to get or modify the context values
What I am actually getting is either the functions that I call are undefined or some other problem ( I tried multiple approaches)
I tried following this video basics of react context in conjunction with this thread How to change Context value while using React Hook of useContext but I am clearly getting something wrong.
Here is what I tried :
return part of App.js
<FeedProvider mf={/* what do i put here */}>
<Navigation>
<HomeScreen />
<ParsedFeed />
<FavScreen />
</Navigation>
</FeedProvider>
Main provider logic
import React, { useState, useEffect, useContext, useCallback } from "react";
import AsyncStorage from "#react-native-async-storage/async-storage";
const FeedContext = React.createContext();
const defaultFeed = [];
const getData = async (keyName) => {
try {
const jsonValue = await AsyncStorage.getItem(keyName);
return jsonValue != null ? JSON.parse(jsonValue) : null;
} catch (e) {
console.log(e);
}
};
const storeData = async (value, keyName) => {
console.log(value, keyName);
try {
const jsonValue = JSON.stringify(value);
await AsyncStorage.setItem(keyName, jsonValue);
} catch (e) {
console.log(e);
}
};
export const FeedProvider = ({ children, mf }) => {
const [mainFeed, setMainFeed] = useState(mf || defaultFeed);
const [feedLoaded, setFeedLoaded] = useState(false);
let load = async () => {
let temp = await AsyncStorage.getItem("mainUserFeed");
temp != null
? getData("mainUserFeed").then((loadedFeed) => setMainFeed(loadedFeed))
: setMainFeed(defaultFeed);
setFeedLoaded(true);
};
useEffect(() => {
load();
}, []);
useCallback(async () => {
if (!feedLoaded) {
return await load();
}
}, [mainFeed]);
const setFeed = (obj) => {
setMainFeed(obj);
storeData(mainFeed, "mainUserFeed");
};
return (
<FeedContext.Provider value={{ getFeed: mainFeed, setFeed }}>
{children}
</FeedContext.Provider>
);
};
//export const FeedConsumer = FeedContext.Consumer;
export default FeedContext;
The custom hook
import { useContext } from "react";
import FeedContext from "./feedProviderContext";
export default function useFeedContext() {
const context = useContext(FeedContext);
return context;
}
What I would hope for is the ability to call the useFeedContext hook anywhere in the app after import like:
let myhook = useFeedContext()
console.log(myhook.getFeed) /// returns the context of the mainFeed from the provider
myhook.setFeed([{test:1},{test:2}]) /// would update the mainFeed from the provider so that mainFeed is set to the passed array with two objects.
I hope this all makes sense, I have spend way longer that I am comfortable to admit so any help is much appreciated.
If you want to keep using your useFeedContext function, I suggest to move it into the your 'Provider Logic' or I'd call it as 'FeedContext.tsx'
FeedContext.tsx
const FeedContext = createContext({});
export const useFeedContext = () => {
return useContext(FeedContext);
}
export const AuthProvider = ({children}) => {
const [mainFeed, setMainFeed] = useState(mf || defaultFeed);
...
return (
<FeedContext.Provider value={{mainFeed, setMainFeed}}>
{children}
</FeedContext.Provider>
);
};
YourScreen.tsx
const YourScreen = () => {
const {mainFeed, setMainFeed} = useFeedContext();
useEffect(() => {
// You have to wait until mainFeed is defined, because it's asynchronous.
if (!mainFeed || !mainFeed.length) {
return;
}
// Do something here
...
}, [mainFeed]);
...
return (
...
);
};
export default YourScreen;

Wrapper is not defined in Jest

I'm trying to test one of my React components but receive the following error:
ReferenceError: wrapper is not defined
describe('TodaysHabits', () => {
let component;
beforeEach(() => {
wrapper = shallow( < TodayHabits / > );
});
test('it exists', () => {
expect(wrapper).toExist;
});
test('it contains an p tag', () => {
const p = wrapper.find('p');
expect(p.text()).toBe('this is display habits');
});
});
index.js
import React from 'react';
import moment from 'moment';
import './todayhabits.css';
class TodayHabits extends React.Component {
// This component will show the habits that the user has to complete this specific day
// To do this we need to make a api call to get all habits where they have the respective day as true in the sql
state = {
dailyHabits: [],
}
//for each habit, it will display a new habit component
//we need to pass a prop to each habit component containing that habit's id and title
displayHabits () {
return <p>this is display habits</p>
}
render() {
return (
<div id='dailyHabits'>
{ moment().format('dddd') }, <br/>
{ moment().format("Do MMMM YYYY") } <br/>
{this.displayHabits()}
<button onClick={() => { history.push(`/newHabit`) }}>Add a habit!</button>
</div>
)
}
}
export default TodayHabits;
I tried doing more research on fixing this error but it wasn't successful.
Can you try declaring wrapper as a variable?
You're not declaring the variable and directly assigning a value to it.
describe('TodaysHabits', () => {
let component;
let wrapper;
beforeEach(() => {
wrapper = shallow( < TodayHabits / > );
});
test('it exists', () => {
expect(wrapper).toExist;
});
test('it contains an p tag', () => {
const p = wrapper.find('p');
expect(p.text()).toBe('this is display habits');
});
});

React hooks array passing in number when passed into props of component

I am currently working on a chat application and for some reason every time I pass in my array of messages as a prop to another component it passes in a number to the component instead of the message object. I have tried a lot of different methods of passing it in regarding using multiple components etc but it seems to still be passing in the number of elements for some reason. Any help is appreciated... code is below
Component receiving the props
import React, { useEffect } from 'react'
import Message from '../../Message/Message'
function Messages({ messages }) {
useEffect(() => {
console.log(messages)
}, [messages])
return (
<div>
test
</div>
)
}
export default Messages
// Import React dependencies.
import React, { useEffect, useState, } from "react";
// Import React dependencies.
import io from 'socket.io-client'
import axios from 'axios'
import Messages from './Messages/Messages'
import uuid from 'react-uuid'
import { Redirect } from 'react-router-dom'
// Import the Slate components and React plugin.
const ENDPOINT = 'http://localhost:5000/'
export const socket = io.connect(ENDPOINT)
const LiveChatFunction = ({ group_id }) => {
// Add the initial value when setting up our state.
const [message, setValue] = useState("")
const [user, setUser] = useState("")
const [groupId, setGroup] = useState('')
const [messages, setMessages] = useState([])
const [toLogin, userAuth] = useState(false)
useEffect(() => {
setGroup(group_id)
axios.post('http://localhost:5000/api/users/refresh_token', null, { withCredentials: true }).then(data => {
if (!data.data.accessToken) {
userAuth(true)
}
})
axios.get('http://localhost:5000/api/users/userInfo', { withCredentials: true }).then(data => {
setUser(data.data.user)
})
socket.on(`message-${group_id}`, data => {
setMessages(messages.push(data))
});
axios.get(`http://localhost:5000/live/${group_id}`).then(x => {
console.log(x.data)
})
}, [group_id, messages])
function setClick() {
const data = {
messageId: uuid(),
user,
groupId,
message
}
socket.emit('message', data)
}
if (toLogin) {
return (
<Redirect to="/login" />
)
}
return (
<div>
<input placeholder="message" type="text" onChange={value => {
setValue(value.target.value)
socket.emit('typing-message', { username: user, time: new Date() })
}} />
<button onClick={setClick}>Submit</button>
<Messages messages={messages} />
</div>
)
}
export default LiveChatFunction;
I have added some comments of what I think you can change:
useEffect(() => {
const recieveFunction = (data) => {
//using callback so no dependency on messages
setMessages((messages) => messages.push(data));
};
async function init() {
//next line is pointless, this runs when group_id
// has changed so something must have set it
// setGroup(group_id);
await axios //not sure if this should be done before listening to socket
.post(
'http://localhost:5000/api/users/refresh_token',
null,
{ withCredentials: true }
)
.then((data) => {
if (!data.data.accessToken) {
userAuth(true);
}
});
await axios
.get('http://localhost:5000/api/users/userInfo', {
withCredentials: true,
})
.then((data) => {
setUser(data.data.user);
});
//start listening to socket after user info is set
socket.on(`message-${group_id}`, recieveFunction);
axios
.get(`http://localhost:5000/live/${group_id}`)
.then((x) => {
console.log(x.data);
});
}
init();
//returning cleanup function, guessing socket.off exists
return () =>
socket.off(`message-${group_id}`, recieveFunction);
}, [group_id]); //no messages dependencies
console.log('messages are now:',messages);
If messages is still not set correctly then can you log it
So I think I found your problem:
In your useEffect hook, you're setting messages to the wrong thing.
socket.on(`message-${group_id}`, data => {
setMessages(messages.push(data))
});
An example:
const m = [].push();
console.log(m);
// m === 0
const n = [].push({});
console.log(n);
// n === 1
As you can see this is the index.
So what you need is:
socket.on(`message-${group_id}`, data => {
messages.push(data);
setMessages(messages);
});
This will set messages to the array of messages.

How do I test methods of React components and include them in my Istanbul coverage?

I was wondering how I'm able to test the methods of my react components and include them in my Istanbul test coverage?
Edit: I'm using enzyme. Forgot to mention that.
For example, I have this component:
class SearchFormContainer extends Component {
static handleToggle = () => {
const filter = document.querySelector('.filter-container');
const root = document.getElementById('root');
if (filter.classList.contains('closed')) {
filter.classList.remove('closed');
filter.classList.add('opened');
root.classList.add('paused');
} else {
root.classList.remove('paused');
filter.classList.remove('opened');
filter.classList.add('closed');
}
};
updateQuantity = (e) => {
const { store } = this.props;
store.setQuantity(e.target.value);
}
updateStrength = (e) => {
const { store } = this.props;
store.setStrength(e.target.value);
}
updateCustomQuantity = (e) => {
const { store } = this.props;
let value = e.target.value || '';
if (!value) {
store.setPricingError('invalidquantity');
} else {
value = value.match(/\d+(\.)?(\d+)?/);
if (!value) {
value = '';
} else {
value = value[0];
}
if (parseFloat(value) <= 0) {
store.setPricingError('invalidquantity');
} else if (store.pricingError === 'invalidquantity') {
store.setPricingError(null);
}
}
store.setCustomQuantity(value);
}
render() {
const {
styleName,
openFilterLabel,
closeFilterLabel,
updateFilterLabel,
searchLabel,
quantityLabel,
strengthLabel,
zipLabel,
zipPlaceholder,
searchFormAnchor,
customQuantityPlaceholder,
store,
searchBar,
} = this.props;
const toggled = 'closed';
const { useCustomQuantity } = store;
let inputType = 'predefined';
if (useCustomQuantity) {
inputType = 'custom';
} else {
inputType = 'predefined';
}
const handleCustomInput = () => {
store.toggleUseCustomQuantity();
};
Here's a test I'm trying to run (note that I've assigned store and searchBar in the describe block.
it('calls upDateQuantity', () => {
sinon.spy(App.prototype, 'updateQuantity');
const updateQuantity = sinon.stub();
const component = shallow(<App
updateQuantity={updateQuantity}
store={store}
searchBar={searchBar}
openFilterLabel="Filter"
closeFilterLabel="Close"
updateFilterLabel="UPDATE"
searchLabel="Medication Name"
quantityLabel="Quantity"
strengthLabel="Strength"
zipLabel="ZIP code"
zipPlaceholder="11111"
searchFormAnchor="SearchForm"
name="search-field"
placeholder="Search drug name..."
customQuantityPlaceholder="Enter Custom Quantity"
/>);
component.find('#quantitySelector').simulate('click');
expect(App.updateQuantity.callCount).to.equal(1);
});
I'm not sure if this will even test the actual function, seems like it will just test to see if the event was fired? I'm getting the error:
TypeError: Attempted to wrap undefined property updateQuantity as function.
I'm not sure how to test certain methods above, such as handleToggle, updateQuantity, UpdateStrength, etc. My react testing skills are young, so any assistance is greatly appreciated. Thank you!
I would suggest using enzyme to render the react component within your test and proceed as follow. You can then test your component methods directly with:
const component = shallow(<MyComponent {...props} />)
component.instance().myMethod()
Or if you need to trigger an event on your component, you can do as follow:
import {shallow} from 'enzyme'
import ButtonControl from '../ButtonControl'
describe('ButtonControl component', () => {
it('handleClick', () => {
let onClickHandler = jest.fn()
let props = { handleClick: onClickHandler }
let component = shallow(<ButtonControl {...props} />)
component.find('button').first().props().onClick()
expect(onClickHandler).toHaveBeenCalled()
})
})
this test use jest plus code coverage. Enzyme is compatible with jasmine, it should be easy to adapt.

Categories

Resources