I have a componentDidMount that executes the fetchUser(). I am trying to test that componentDidMount.
The Component Code:
static propTypes = {
match: PropTypes.shape({
isExact: PropTypes.bool,
params: PropTypes.object,
path: PropTypes.string,
url: PropTypes.string
}),
label: PropTypes.string,
actualValue: PropTypes.string,
callBack: PropTypes.func
};
state = {
user: {}
};
componentDidMount() {
this.fetchUser();
}
getUserUsername = () => {
const { match } = this.props;
const { params } = match;
return params.username;
};
fetchUser = () => {
getUser(this.getUserUsername()).then(username => {
this.setState({
user: username.data
});
});
};
The Test:
it('should call fetchUsers function only once', () => {
const match = { params: { username: 'testUser' }, isExact: true, path: '', url: '' };
const fetchUserFn = jest.fn(match);
const wrapper = shallow(<UserDetailsScreen match={match} fetchUsers={fetchUserFn} />);
wrapper.instance().componentDidMount(match);
expect(fetchUserFn).toHaveBeenCalledTimes(1); // I get expected 1 and got 0
});
I mean why is this componentDidMount(), testing different than my other ones? I have tested quite a few of them over the past few weeks, never this issue. Maybe because the getUser() is a promise and I need to mock it. Has anyone stumpled on something like this before?
The code for the getUser()
export const getUser = username => {
const options = {
method: httpMethod.GET,
url: endpoint.GET_USER(username)
};
return instance(options);
};
I found the solution, by using spyOn(), by jest. Not sure why, I needed to spy for this particular use-case but please explain if you can. The solution below:
it('should call fetchUsers function only once', () => {
const match = { params: { username: 'testUser' }, isExact: true, path: '', url: '' };
const fetchUserFn = jest.fn(match);
const spy = jest.spyOn(UserDetailsScreen.prototype, 'componentDidMount');
const wrapper = shallow(<UserDetailsScreen match={match} fetchUsers={fetchUserFn} />, {
disableLifecycleMethods: true
});
wrapper.instance().componentDidMount(match);
expect(spy).toHaveBeenCalledTimes(1);
});
One caveat here. If you do not user disableLifecycleMethods, the function will be called twice. Once for every render if I am not mistaken.
Related
This is my test code snippet but it throws an exception, TypeError: componentInstance.loadLoanApplication is not a function :
it('should render the SubmittedLoan', () => {
const loanData = {
data: {
id: 1,
};
const div = document.createElement('div');
const wrapper = mount(
<AppProviders>
<MemoryRouter initialEntries={['/review/153']}>
<SubmittedLoan
match={{ params: { loanId: 1, step: 1 } }}
history={{
location: { state: { from: 'register' } },
push() {},
}}
/>
</MemoryRouter>
</AppProviders>,
div,
);
const componentInstance = wrapper
.find(SubmittedLoan)
.children()
.first()
.children()
.first()
.instance();
const loanApplication = {
id: 1,
steps_data: [
{ slug: 'step_1', title: 'Step 1' },
{ slug: 'step_2', title: 'Step 2' },
],
status: ApiCaptiq.STATUS_SUBMITTED,
};
expect(wrapper.find(SubmittedLoan).length).toBe(1);
componentInstance.loadLoanApplication(1, 1);
componentInstance.onLoadLoanApplication(loanData);
componentInstance.onLoadFail();
componentInstance.setState({
formData: [{ item: 'value' }, { item2: 'value2' }],
activeStep: 1,
loanApplication,
});
componentInstance.handleSnackbarClose(new Event('click'), '');
componentInstance.setState({ activeStep: 3 });
});
Then my Component which uses memo is as follows :
export const SubmittedLoan = memo(() => {
const [loanApplication, setLoanApplication] = useState<LoanApplication | null>(null);
const [message, setMessage] = useState({
message: '',
open: false,
messageType: '',
});
const authContext = useContext(AuthContext);
const customerContext = useCustomerData();
const params = useParams();
const history = useHistory();
const classes = useStyles();
const { loanId } = params;
const onLoadFail = useCallback(() => {
setMessage({
message: 'Die verfügbaren Darlehensarten können nicht aufgelistet werden',
open: true,
messageType: 'error',
});
}, []);
const onLoadLoanApplication = useCallback(
(response: AxiosResponse) => {
setTemplateSettings(response, authContext);
if (
response.data.status === ApiCaptiq.STATUS_STARTING ||
response.data.status === ApiCaptiq.STATUS_IN_PROGRESS ||
response.data.status === ApiCaptiq.STATUS_PRE_WAITING
) {
history.push(`/view/${loanId}`);
} else {
setLoanApplication(response.data);
}
},
[loanId, authContext, history],
);
const loadLoanApplication = useCallback(
async (loan_id: number) => {
try {
const response = await request.get(`${ApiCaptiq.LOAN_APPLICATION_URL}${loan_id}/`);
const { fetchCustomerProfile } = customerContext;
await fetchCustomerProfile(response.data.customer_profile_id);
onLoadLoanApplication(response);
} catch (err) {
onLoadFail();
}
},
[customerContext, onLoadLoanApplication, onLoadFail],
);
...
What could be the possible reason for this
The functions you are defining inside the component, are not just available on the component instance. In fact, there is not way to call them. You can test only by mocking the fetch calls they are doing.
If you really need callable functions in your component (you should try to avoid these..), you could use this: https://reactjs.org/docs/hooks-reference.html#useimperativehandle
Perhaps better would be to extract this data loading logic elsewhere and test it separately.
I'm trying to test async service that is returning response object after few intertwined API calls (axios with interceptors). Right now I'm using jest-mock-axios lib but I'm open to any alternatives or pure Jest.
(I removed irrelevant parts of code, originally written in TS)
// services/persons.js
import personsAgent from '../agents/persons';
import places from './places';
[...]
const get = async ({ search = '', limit, offset }) => {
const places = await places.get({ search: '', limit: 1000, offset: 0 }); // api call to endpoint url '/places/'
const params = {
search: !!search.length ? search : null,
limit,
offset,
};
[...]
return personsAgent.getAll({ ...params }).then(resp => {
const results = sort(resp.data.results, .....).map((person, i) => {
const place = places?.data?.results.filter(.....);
return {
id: person.id,
name: person.first_name,
surname: person.last_name,
place,
};
});
return {
data: { ...resp.data, results },
status: resp.status,
};
});
};
[....]
export default {
get,
};
// agents/persons.js
import requests from '../utils/axios';
export default {
getAll: (params: object) => requests.get('/persons/', { params }),
}
// services/persons.test.js
import mockAxios from 'jest-mock-axios';
import persons from './persons';
afterEach(() => {
mockAxios.reset();
});
it('returns Persons data from API', async () => {
let catchFn = jest.fn(),
thenFn = jest.fn();
persons
.get({ search: '', limit: 10, offset: 0 })
.then(thenFn)
.catch(catchFn);
expect(mockAxios.get).toHaveBeenCalledWith('/persons/', {
params: { search: null, limit: 10, offset: 0 },
}); // FAIL - received: '/places/', { search: '', limit: 1000, offset: 0 }
let responseObj = {
data: {
results: ['test'],
},
};
mockAxios.mockResponse(responseObj);
expect(thenFn).toHaveBeenCalledWith({
data: {
results: ['test'],
},
status: 200,
});
expect(catchFn).not.toHaveBeenCalled();
});
I'm using jest-mock-axios and for my others, simpler services without additional, internal call everything is working fine, but this is problematic.
How to ignore or mock const places = await places.get() to focus on personsAgent.getAll()?
Issue right now is that I'm testing request for const places = await places.get() and there is no secondary request for personsAgent.getAll().
axios.getReqByUrl('/persons/') // null
Any ideas, examples or alternatives? Thx in advance!
I want to unit test my vue components. Since I'm working with firebase this is a little bit difficult.
Fir of all, I created a __mocks__ folder to contain all my mocked functions. Inside that folder, I've created firebase.js:
import * as firebase from 'firebase';
const onAuthStateChanged = jest.fn();
const getRedirectResult = jest.fn(() => Promise.resolve({
user: {
displayName: 'redirectResultTestDisplayName',
email: 'redirectTest#test.com',
emailVerified: true,
},
}));
const sendEmailVerification = jest.fn(() => Promise.resolve('result of sendEmailVerification'));
const sendPasswordResetEmail = jest.fn(() => Promise.resolve());
const createUserWithEmailAndPassword = jest.fn(() => {
console.log('heeeeelllo');
Promise.resolve({
user: {
displayName: 'redirectResultTestDisplayName',
email: 'redirectTest#test.com',
emailVerified: true,
},
});
});
const signInWithEmailAndPassword = jest.fn(() => Promise.resolve('result of signInWithEmailAndPassword'));
const signInWithRedirect = jest.fn(() => Promise.resolve('result of signInWithRedirect'));
const initializeApp = jest // eslint-disable-line no-unused-vars
.spyOn(firebase, 'initializeApp')
.mockImplementation(() => ({
auth: () => ({
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
currentUser: {
sendEmailVerification,
},
signInWithRedirect,
}),
}));
jest.spyOn(firebase, 'auth').mockImplementation(() => ({
onAuthStateChanged,
currentUser: {
displayName: 'testDisplayName',
email: 'test#test.com',
emailVerified: true,
},
getRedirectResult,
sendPasswordResetEmail,
}));
firebase.auth.FacebookAuthProvider = jest.fn(() => {});
firebase.auth.GoogleAuthProvider = jest.fn(() => {});
This file, I took from: https://github.com/mrbenhowl/mocking-firebase-initializeApp-and-firebase-auth-using-jest
The component I want to test is called EmailSignupLogin. In this particular case, I want to test the registerViaEmail-method:
methods: {
registerViaEmail() {
if (this.password.length > 0 && this.password === this.passwordReenter) {
firebase.auth().createUserWithEmailAndPassword(this.emailAdress, this.password).then((result) => {
const { user } = result;
console.log(result);
this.setUser(user);
this.$router.push('/stocks');
}).catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
this.error = errorMessage;
console.error(errorCode, errorMessage);
});
} else {
this.error = 'passwords not matching';
}
},
},
Now to my test file(email-signup-login.spec.js):
import { mount } from '#vue/test-utils';
import Vue from 'vue';
import EmailSignupLogin from '#/components/email-signup-login';
jest.mock('../../__mocks__/firebase');
describe('EmailSignupLogin', () => {
let wrapper;
const mockFunction = jest.fn();
beforeEach(() => {
wrapper = mount(EmailSignupLogin, {
data() {
return {
password: '123456',
passwordReenter: '123456',
emailAdress: 'test#test.com',
};
},
store: {
actions: {
setUser: mockFunction,
},
},
});
});
describe('methods', () => {
describe('#registerViaEmail', () => {
it('calls mockFunction', async () => {
await wrapper.vm.registerViaEmail();
expect(mockFunction).toHaveBeenCalled();
});
});
});
});
Inside the registerViaEmail-method I call the setUser-action, which is a vuex-action.
The problem is that it doesn't seem to call my mocked functions from __mocks__/firebase.js. Can somebody please tell me why?
Several issues turned up in your code:
registerViaEmail() is not async (not returning a Promise), so the await call returns prematurely, at which point your test tries to assert something that hasn't occurred yet. To resolve this, just wrap the function body with a Promise:
registerViaEmail() {
return new Promise((resolve, reject) => {
if (this.password.length > 0 && this.password === this.passwordReenter) {
firebase.auth().createUserWithEmailAndPassword(this.emailAdress, this.password).then((result) => {
//...
resolve()
}).catch((error) => {
//...
reject()
});
} else {
//...
reject()
}
})
},
The script you referred to is not intended to be used with Jest __mocks__. The script itself directly modifies the firebase object, replacing its methods/properties with mocks. To use the script, you just need to import it before importing the test module that uses firebase:
import './firebase-mock' // <-- order important
import EmailSignupLogin from '#/components/EmailSignupLogin'
createUserWithEmailAndPassword does not return anything. It looks like it originally returned the Promise, but you modified it with a console.log, and forgot to continue returning the Promise, which prevented this method from being awaited (same issue as #1). The solution there is to return the Promise:
const createUserWithEmailAndPassword = jest.fn(() => {
console.log('heeeeelllo')
return /*👈*/ Promise.resolve(/*...*/)
})
createUserWithEmailAndPassword is the method to be tested in EmailSignupLogin, but it's currently not mocked in your auth mock object. It's only mocked in the return of initializeApp.auth, but that's not what it being used in EmailSignupLogin. To resolve the issue, copy createUserWithEmailAndPassword to your auth mock object:
jest.spyOn(firebase, 'auth').mockImplementation(() => ({
onAuthStateChanged,
currentUser: {
displayName: 'testDisplayName',
email: 'test#test.com',
emailVerified: true,
},
getRedirectResult,
sendPasswordResetEmail,
createUserWithEmailAndPassword, //👈
}));
In your test setup, you mocked the store with a plain object, but it actually needs to be an instance of Vuex.Store:
mount({
//store: { /*...*/ }, //❌DON'T DO THIS
store: new Vuex.Store({ /*...*/ }) //✅
})
Github demo
I want to test Stripe function createToken, and check if it is saved into the state in React but I can not find a way to mock the response.
So far I have tried to mock the function with jest but when I run the test I can see that the function is never called.
Here is my Parent component. On the child component there is a button that activate the function "sendPayment". In the child component I tested if this function was called, so now the idea is to create a wrapper with enzyme and run the function.
var stripe = require('stripe-client')(STRIPE_PUBLIC_KEY);
class StripePayment extends React.Component {
constructor(props) {
super(props);
this.state = {
number: '',
exp_year: '',
exp_month: '',
cvc: '',
error: '',
token: ''
};
}
handleOnChangeText = (text, cardAttribute) => {
this.setState({
[cardAttribute]: text,
});
};
sendPayment = () => {
//
stripe.createToken(this.createCard())
.then(resp => this.setState({token: resp.id}))
.catch(error =>
this.setState({
error,
})
);
};
createCard = () => {
let informations = {};
information.card = {};
Object.keys(this.state)
.filter(key => !['error','token'].includes(key))
.forEach(filteredKey => (information.card[filteredKey] = this.state[filteredKey]));
return informations;
};
render() {
return <StripeForm sendPayment={this.sendPayment} error={this.state.error} />;
}
}
StripePayment.navigationOptions = {
header: null,
};
export default StripePayment;
The test I wrote so far:
it('should save token to state', async () => {
const wrapper = shallow(<StripePaymentScreen />)
const instance = wrapper.instance()
const response = {id: 'token'}
jest.spyOn(stripe, 'createToken').mockImplementation(() => response);
await instance.sendPayment();
expect(stripe.createToken).toHaveBeenCalled();
expect(instance.state.data).toEqual({ token: 'token' });
});
Or alternatively
describe('StripePayment', () => {
let stripe;
beforeEach(() => {
stripe = {
createToken : jest.fn()
};
});
it('should save token to state', async () => {
const wrapper = shallow(<StripePaymentScreen />)
const instance = wrapper.instance()
const response = {id: 'token'}
jest.spyOn(stripe, 'createToken').mockImplementation(() => response);
stripe.createToken.mockResolvedValue(response);
await instance.sendPayment();
expect(stripe.createToken).toHaveBeenCalled();
expect(instance.state.data).toEqual({ token: 'token' });
});
});
I am trying to call actions on an array ob objects.The action is called on the onSuccess method of a previous action. When calling the action on a single object I am able to execute everything properly but it never executes when I call them in a forEach or a for Loop.
this is my import
import {createClient} from "../../store/actions/clients/clients.actions";
import {createNewContact} from "../../store/actions/contact-list/contact-list.actions";
this is my connect decorator
#connect(
state => ({avatarURL: state.avatar.URL}),
dispatch => bindActionCreators({modalVisibility, updateAvatarURL, createClient, createNewContact}, dispatch)
)
this is my two actions getting called
this.props.createClient({
newClientData, onSuccess: (newClientData) => {
for (let contact of values.contacts) {
contact.clientId = newClientData.id;
this.props.createNewContact(contact);
}
this.props.modalVisibility(false);
}
});
this is my action
export const createNewContact = (params) => {
debugger
return dispatch => {
let user = {
email: params.email,
name: params.firstName,
surname: params.lastName,
password: params.password,
clientId: params.clientId
};
let onSuccess = () => {
dispatch({
type: actions.CREATE_CLIENT_SUCCESS,
payload: {...params.newClientData}
});
if (params.onSuccess) params.onSuccess({...params.newClientData});
};
let onFailure = () => {
dispatch({
type: actions.CREATE_CLIENT_FAILURE,
payload: error
});
if (params.onFailure) params.onFailure(error);
};
awsServices.createUserInTheGroup({...user, group: config.groups.clients}, onSuccess, onFailure);
};
};
thanks in advance!