I am trying to write test cases of react component in Mocha + Enzyme. I have used Sinon to spy document elements.
In a react component method i have below statement, where trying to add a value for Select element on Button click event.
handleClick(e){
e.preventDefault();
if(e.target.value!='select')
this.setState({
selectedValue: e.target.value
})
else
document.getElementsByName("foodSelector")[0].value="select";
}
So i tried mocking this method as below using Mocha,
describe('(Container) ReactContainer', () => {
beforeEach(function(){
wrapper = shallow(<SelectContainer/>);
})
it('should trigger change event when policy dropdown is clicked', function(){
const selectElt = wrapper.find('#foodSelector');
selectElt.simulate('change', { preventDefault() {}, target: { value: 'TestingValue' } });
selectElt.simulate('change', { preventDefault() {}, target: { value: 'select' } });
});
});
I am getting an error as 'TypeError: Cannot set property 'value' of undefined', so can anyone help me on how to mock the document.getElementById?
I tried using sinon as below,
var selectElt = document.createElement('select');
selectElt.id = 'foodSelector';
var f = sinon.stub(document, 'getElementById').withArgs('foodSelector').returns(selectElt);
expect(f.value).to.be.equal('select');
Even then i am getting the same error as below,
TypeError: Cannot set property 'value' of undefined
Related
I have a computed property that asks server for user data and then the other one that computes number of users. To propagate changes into the template, I'm using DS.PromiseArray wrapper. With this wrapper, I can't find an easy way to test this property.
// model
users: computed('name', function () {
let name = get(this, 'name');
return DS.PromiseArray.create({
promise: this.store.query('user', { name })
});
}),
numberOfUsers: computed('users', function () {
return get(this, 'users.length') || 0;
}),
// ....
test('numberOfUsers returns correct number', function (assert) {
let model = this.subject({
store: EmberObject.create({
query() {
return Promise.resolve([
{ name: 'Thomas' },
{ name: 'Thomas' },
{ name: 'Thomas' },
]);
}
}),
name: 'Thomas',
});
assert.equal(model.get('numberOfUsers'), 3);
});
This test fails with 0 !== 3. Any ideas?
Since model.get('users') is a promise, model.get('numberOfUsers') will not be 3 until the Promise resolves. In your test, you're immediately calling model.get('numberOfUsers'), and that is using the initial value of model.get('users'), which is an unresolved promise.
To get it to work, you could call users and put your assert inside the then of that returned promise:
model.get('users').then((user) => {
assert.equal(model.get('numberOfUsers'), 3);
})
A couple of side notes:
It is conventional in Ember to do your data fetching in the Route's model hook, not in a component like this.
Also, there's no need to manually create a PromiseArray in your application code, because an Ember Data query returns a type of Promise Array already. So you can just return this.store.query('user', { name }); (If you do this, you'll have to change your test query stub to generate a PromiseArray).
Project: https://github.com/marioedgar/webpack-unit-test
I have a Vue.js app I generated with the vue CLI. I've only edited the HelloWorld component slightly to fetch some async data from my test service, you can see that here:
<template>
<h1>{{ message }}</h1>
</template>
<script>
import service from './test.service'
export default {
name: 'HelloWorld',
created () {
service.getMessage().then(message => {
this.message = message
})
},
data () {
return {
message: 'A'
}
}
}
</script>
<style scoped>
</style>
The test service lives in the same directory and is very simple:
class Service {
getMessage () {
return new Promise((resolve, reject) => {
console.log('hello from test service')
resolve('B')
})
}
}
const service = new Service()
export default service
So in order to mock this service, im using the webpack vue-loader to inject the mock service as described in the official documentation here:
https://vue-loader.vuejs.org/en/workflow/testing-with-mocks.html
So here is my test which is almost identical to the example:
import Vue from 'vue'
const HelloInjector = require('!!vue-loader?inject!../../../src/components/HelloWorld')
const Hello = HelloInjector({
// mock it
'./test.service': {
getMessage () {
return new Promise((resolve, reject) => {
resolve('C')
})
}
}
})
describe('HelloWorld.vue', () => {
it('should render', () => {
const vm = new Vue({
template: '<div><test></test></div>',
components: {
'test': Hello
}
}).$mount()
expect(vm.$el.querySelector('h1').textContent).to.equal('C')
})
})
There are two issues i am facing:
The test fails because the assertion is executing before the mocked promise is resolved. From my understanding, this is because the vue lifecycle hasnt completely finished when im doing my asserting. The common patter to wait for the next cycle would be to wrapp my assertion around the next tick function like this:
it('should render', (done) => {
const vm = new Vue({
template: '<div><test></test></div>',
components: {
'test': Hello
}
}).$mount()
Vue.nextTick(() => {
expect(vm.$el.querySelector('h1').textContent).to.equal('C')
done()
})
})
This, however, does not work unless I nest 3 nextTicks, which seems extremely hacky to me. Is there something I am missing to get this to work? this example seems extremely straightforward, but I cannot get this test to pass without lots of nextTicks
I keep getting a strange error, intermittently... this warning shows up probably 50% of the time and is not consistant at all.
[vue warn] Failed to mount component: template or render function is not defined
Again, this happens only sometimes. I can run the same exact unit test without any changes and it will show me this message 50% of the time.
I truly couldn't figure out why sometimes the component failed to mount. I'm not even sure it's related to the injector but, in any case, I kept the test consistent by not using it; trying a different approach instead.
The component might be more testable if the service is injected through the props instead of being directly used.
<template>
<div>
<h1>{{ message }}</h1>
</div>
</template>
<script>
import service from './test.service'
export default {
name: 'HelloWorld',
created () {
this.service.getMessage().then(message => {
this.message = message
})
},
data () {
return {
message: 'A'
}
},
props: {
service: {
default: service
}
}
}
</script>
<style scoped>
</style>
This makes the injector unnecessary as the mocked service can be instead passed to the component using propsData in the constructor.
import Vue from 'vue'
import Async from '#/components/Async'
const service = {
getMessage () {
return new Promise((resolve, reject) => {
resolve('C')
})
}
}
describe('Async.vue', () => {
let vm
before(() => {
const Constructor = Vue.extend(Async)
vm = new Constructor({
propsData: {
service: service
}
}).$mount()
})
it('should render', function () {
// Wrapping the tick inside a promise, bypassing PhantomJS's lack of support
return (new Promise(resolve => Vue.nextTick(() => resolve()))).then(() => {
expect(vm.$el.querySelector('h1').textContent).to.equal('C')
})
})
})
Recently I am learning to test React with jest and enzyme, It seems hard to understand what a unit test is it, my code
import React from "react";
class App extends React.Component {
constructor() {
super();
this.state = {
value: ""
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
const value = e.target.value;
this.setState({
value
});
}
render() {
return <Nest value={this.state.value} handleChange={this.handleChange} />;
}
}
export const Nest = props => {
return <input value={props.value} onChange={props.handleChange} />;
};
export default App;
and my test
import React from "react";
import App, { Nest } from "./nest";
import { shallow, mount } from "enzyme";
it("should be goood", () => {
const handleChange = jest.fn();
const wrapper = mount(<App />);
wrapper.find("input").simulate("change", { target: { value: "test" } });
expect(handleChange).toHaveBeenCalledTimes(1);
});
IMO, the mocked handleClick will intercept the handleClick on App,
if this is totally wrong, what's the right way to use mock fn and test the handleClick be called.
Another: I search a lot, read the similar situations, seem like this iscontra-Unit Test,
Probably I should test the two component separately, I can test both components,
test the
<Nest value={value} handleChange={handleChange} />
by pass the props manually, and then handleChangeinvoked by simulate change
it passed test.
but how can I test the connection between the two?
I read
some work is React Team's Work
...
I don't know which parts I have to test in this case, and Which parts react already tested and don't need me to test. That's confusing.
You should take the path of testing the Nest component in isolation first, passing your mocked handleChange as a prop, to verify that input changes are being propagated.
If you want to test the state part, then you can get the instance of your App class from enzyme and call that method directly:
it("should update the Nest value prop when change is received", () => {
const wrapper = mount(<App />);
const instance = wrapper.instance()
instance.handleChange( { target: { value: "test" } })
const nestComponent = wrapper.find("Nest").first()
expect(nestComponent).prop('value').toEqual('test');
});
This a very very basic, almost not needed to test piece of code, but it will get your test coverage up if that's what you're after.
Doc for instance: http://airbnb.io/enzyme/docs/api/ReactWrapper/instance.html
If you want to test for the connection. From what I see, the nest component is a child component inside the App component. You could test that <App /> contains `.
describe('<App />', () => {
it('should contain a nest component', () => {
const wrapper = mount(<App />);
expect(wrapper.find(<Nest />)).toHaveLength(1);
});
});
Secondly, since the onChange event on the nest component updates the state in the App component, you can also test for state changes since its a behavior you expect.
it('should update state', () => {
//find input and simulate change with say {value: 'new value'} and then
expect(wrapper.state().value).toBe('newValue');
});
I hope this helps.
I am new to redux testing and have been trying to back fill test for an application I made so sorry if this is the complete wrong way to go about testing with nock and redux-mock-store.
//Action in authAction.js
export function fetchMessage() {
return function(dispatch) {
axios.get(ROOT_URL, {
headers: { authorization: localStorage.getItem('token') }
})
.then(response => {
console.log("hi")
dispatch({
type: FETCH_MESSAGE,
payload: response.data.message
});
})
.catch(response => {
console.log(response)
//callingRefresh(response,"/feature",dispatch);
});
}
}
This is the method and it seems to be getting called but normally goes to the catch cause of nock failing cause of the header not matching.
//authActions_test.js
import nock from 'nock'
import React from 'react'
import {expect} from 'chai'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
const middlewares = [ thunk ]
const mockStore = configureMockStore(middlewares)
import * as actions from '../../src/actions/authActions';
const ROOT_URL = 'http://localhost:3090';
describe('actions', () => {
beforeEach(() => {
nock.disableNetConnect();
localStorage.setItem("token", '12345');
});
afterEach(() => {
nock.cleanAll();
nock.enableNetConnect();
});
describe('feature', () => {
it('has the correct type', () => {
var scope = nock(ROOT_URL).get('/',{reqheaders: {'authorization': '12345'}}).reply(200,{ message: 'Super secret code is ABC123' });
const store = mockStore({ message: '' });
store.dispatch(actions.fetchMessage()).then(() => {
const actions = store.getStore()
expect(actions.message).toEqual('Super secret code is ABC123');
})
});
});
});
Even when the header is removed and the nock intercepts the call. I am getting this error every time
TypeError: Cannot read property 'then' of undefined
at Context.<anonymous> (test/actions/authActions_test.js:43:24)
You're not returning the promise from axios to chain the then call onto.
Change the thunk to be like:
//Action in authAction.js
export function fetchMessage() {
return function(dispatch) {
return axios.get(ROOT_URL, {
headers: { authorization: localStorage.getItem('token') }
})
.then(response => {
console.log("hi")
dispatch({
type: FETCH_MESSAGE,
payload: response.data.message
});
})
.catch(response => {
console.log(response)
//callingRefresh(response,"/feature",dispatch);
});
}
}
You may also need to change the test so that it doesn't pass before the promise resolves. How to do this changes depending on which testing library you use. If you're using mocha, take a look at this answer.
Side note: I'm not sure if you have other unit tests testing the action creator separately to the reducer, but this is a very integrated way to test these. One of the big advantages of Redux is how easily each seperate cog of the machine can be tested in isolation to each other.
I want to show a loading spinner in an ember component when I load some data from the store. If there is an error from the store, I want to handle it appropriately.
Here is a snippet of my current code:
cachedCampaignDataIsPending: computed.readOnly('fetchCachedCampaignData.isPending'),
fetchCachedCampaignData: computed('campaign', function() {
return this.get('store').findRecord('cachedCampaignMetric').then( (cachedMetrics) => {
this.set('cachedCampaignData', cachedMetrics);
}, () => {
this.set('errors', true);
});
}),
This will properly set the errors to true if the server responds with an error, however, the cachedCampaignDataIsPending is not acting properly. It does not get set to true.
However, if I rewrite my code to make the computed property not be thenable, then it does fire propertly.
fetchCachedCampaignData: computed('campaign', function() {
return this.get('store').findRecord('cachedCampaignMetric');
}),
Anyone know the reason why, or how to make it fire properly, using the then/catch logic?
Thanks.
Update
I found that this works for me and gives me what I need.
cachedCampaignDataIsPending: computed.readOnly('fetchCachedCampaignData.isPending'),
fetchCachedCampaignData: computed('campaign', function() {
let promise = this.get('store').findRecord('cachedCampaignMetric');
promise.then( (cachedMetrics) => {
this.set('cachedCampaignData', cachedMetrics);
}, () => {
this.set('errors', true);
});
return promise;
}),