I have an Angular2 component that contains a select box that looks like
<select [(ngModel)]="envFilter" class="form-control" name="envSelector" (ngModelChange)="onChangeFilter($event)">
<option *ngFor="let env of envs" [ngValue]="env">{{env}}</option>
</select>
I am trying to write a unit test for the ngModelChange event. This is my latest failing attempt
it("should filter and show correct items", async(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
el = fixture.debugElement.query(By.name("envSelector"));
fixture.detectChanges();
makeResponse([hist2, longhist]);
comp.envFilter = 'env3';
el.triggerEventHandler('change', {});
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(comp.displayedHistory).toEqual(longhist);
});
});
The part I am having trouble with is that changing the value of the underlying model comp.envFilter = 'env3'; does not trigger the change method. I added el.triggerEventHandler('change', {}); but this throws Failed: Uncaught (in promise): ReferenceError: By is not defined. I cannot find any hints in the documentation... any ideas?
As far as the error. It seems like you just need to import By. This is not something that is global. It should be imported from the following module
import { By } from '#angular/platform-browser';
As far as the testing part, this is what I have been able to figure out. When you change a value in a the component, you need to trigger a change detection to update the view. You do this with fixture.detectChanges(). Once this is done, normally the view should be updated with the value.
From testing something similar to your example, it seems this is not the case though. It seems there is still some asynchronous task going on after the change detection. Say we have the following
const comp = fixture.componentInstance;
const select = fixture.debugElement.query(By.css('select'));
comp.selectedValue = 'a value';
fixture.DetectChanges();
expect(select.nativeElement.value).toEqual('1: a value');
This doesn't seem to work. It appears there is some async going on causing the value not to be set yet. So we need to wait for the async tasks by calling fixture.whenStable
comp.selectedValue = 'a value';
fixture.DetectChanges();
fixture.whenStable().then(() => {
expect(select.nativeElement.value).toEqual('1: a value');
});
The above would work. But now we need to trigger the change event as that doesn't happen automatically.
fixture.whenStable().then(() => {
expect(select.nativeElement.value).toEqual('1: a value');
dispatchEvent(select.nativeElement, 'change');
fixture.detectChanges();
fixture.whenStable().then(() => {
// component expectations here
});
});
Now we have another asynchronous task from the event. So we need to stabilize it again
Below is a complete test that I tested with. It's a refactor of the example from the source code integration tests. They used fakeAsync and tick which is similar to using async and whenStable. But with fakeAsync, you can't use templateUrl, so I though it would be best to refactor it to use async.
Also the source code tests does kind of a double one way testing, first testing model to view, then view to model. While it looks like your test was trying to do kind of a two-way test, from model around back to model. So I refactored it a bit to suite your example better.
import { Component } from '#angular/core';
import { TestBed, getTestBed, async } from '#angular/core/testing';
import { FormsModule } from '#angular/forms';
import { By } from '#angular/platform-browser';
import { dispatchEvent } from '#angular/platform-browser/testing/browser_util';
#Component({
selector: 'ng-model-select-form',
template: `
<select [(ngModel)]="selectedCity" (ngModelChange)="onSelected($event)">
<option *ngFor="let c of cities" [ngValue]="c"> {{c.name}} </option>
</select>
`
})
class NgModelSelectForm {
selectedCity: {[k: string]: string} = {};
cities: any[] = [];
onSelected(value) {
}
}
describe('component: NgModelSelectForm', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ FormsModule ],
declarations: [ NgModelSelectForm ]
});
});
it('should go from model to change event', async(() => {
const fixture = TestBed.createComponent(NgModelSelectForm);
const comp = fixture.componentInstance;
spyOn(comp, 'onSelected');
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'Buffalo'}];
comp.selectedCity = comp.cities[1];
fixture.detectChanges();
const select = fixture.debugElement.query(By.css('select'));
fixture.whenStable().then(() => {
dispatchEvent(select.nativeElement, 'change');
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(comp.onSelected).toHaveBeenCalledWith({name : 'NYC'});
console.log('after expect NYC');
});
});
}));
});
I found peeskillet's answer very useful but sadly it is a little out of date as the way to dispatch an Event has been changed. I also found there was an unnecessary call to whenStable(). So here is an updated test using peeskillet's setup:
it('should go from model to change event', async(() => {
const fixture = TestBed.createComponent(NgModelSelectForm);
const comp = fixture.componentInstance;
spyOn(comp, 'onSelected');
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'Buffalo'}];
comp.selectedCity = comp.cities[1];
fixture.detectChanges();
const select = fixture.debugElement.query(By.css('select'));
fixture.whenStable().then(() => {
select.nativeElement.dispatchEvent(new Event('change'));
fixture.detectChanges();
expect(comp.onSelected).toHaveBeenCalledWith({name : 'NYC'});
console.log('after expect NYC');
});
}));
Look this example, from angular source (template_integration_spec.ts)
#Component({
selector: 'ng-model-select-form',
template: `
<select [(ngModel)]="selectedCity">
<option *ngFor="let c of cities" [ngValue]="c"> {{c.name}} </option>
</select>
`
})
class NgModelSelectForm {
selectedCity: {[k: string]: string} = {};
cities: any[] = [];
}
it('with option values that are objects', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelSelectForm);
const comp = fixture.componentInstance;
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'Buffalo'}];
comp.selectedCity = comp.cities[1];
fixture.detectChanges();
tick();
const select = fixture.debugElement.query(By.css('select'));
const nycOption = fixture.debugElement.queryAll(By.css('option'))[1];
// model -> view
expect(select.nativeElement.value).toEqual('1: Object');
expect(nycOption.nativeElement.selected).toBe(true);
select.nativeElement.value = '2: Object';
dispatchEvent(select.nativeElement, 'change');
fixture.detectChanges();
tick();
// view -> model
expect(comp.selectedCity['name']).toEqual('Buffalo');
}));
Same problem as raised by OP but slightly different code.
Works in Angular 7.
HTML:
<select id="dashboard-filter" class="form-control" name="dashboard-filter" [ngModel]="dashboardFilterValue" (ngModelChange)="onFilterChange($event)"
[disabled]="disabled">
<option *ngFor="let filter of dashboardFilters" [ngValue]="filter.value">{{ filter.name }}</option>
</select>
Unit test:
it('onFilterChange', () => {
// ensure dropdown is enabled
expect(component.disabled).toBe(false)
// spies
spyOn(component, 'onFilterChange').and.callThrough()
spyOn(component.filterChange, 'emit')
// initially the 3rd item in the dropdown is selected
const INITIAL_FILTER_INDEX = 2
// we want to select the 5th item in the dropdown
const FILTER_INDEX = 4
// the expected filter value is the value of the 5th dashboard filter (as used to populate the dropdown)
const EXPECTED_FILTER_VALUE = getDashboardFiltersData.dashboardFilters[FILTER_INDEX].value
// handle on the dropdown
const filterDropdown = fixture.debugElement.query(By.css('select')).nativeElement
// let bindings complete
fixture.whenStable().then(() => {
// ensure filterDropdown.value is stable
expect(filterDropdown.value).toContain(getDashboardFiltersData.dashboardFilters[INITIAL_FILTER_INDEX].value)
// update filterDropdown.value and dispatch change event
filterDropdown.value = filterDropdown.options[FILTER_INDEX].value
filterDropdown.dispatchEvent(new Event('change'))
// check component data
expect(component.dashboardFilterValue).toBe(EXPECTED_FILTER_VALUE)
expect(component.dashboardFilterChangeInProgress).toBe(false)
// check spies
expect(component.onFilterChange).toHaveBeenCalledWith(EXPECTED_FILTER_VALUE)
expect(setDashboardFilterSpy).toHaveBeenCalledWith(EXPECTED_FILTER_VALUE)
expect(component.filterChange.emit).toHaveBeenCalledWith(true)
})
})
Related
I have a sample code for checkbox written with Angular2.
<div class="col-sm-7 align-left" *ngIf="Some-Condtion">
<input type="checkbox" id="mob_Q1" value="Q1" />
<label for="mob_Q1">Express</label>
</div>
I want to unit test the above checkbox. Like I want to recognize the checkbox and test whether it is check-able. How do I unit test this with Karma Jasmine?
Component, e.g. CheckboxComponent, contains input element. Unit test should looks like:
import {ComponentFixture, TestBed} from '#angular/core/testing';
import {By} from '#angular/platform-browser';
import {CheckboxComponent} from './checkbox.component';
describe('Checkbox test.', () => {
let comp: CheckboxComponent;
let fixture: ComponentFixture<CheckboxComponent>;
let input: Element;
beforeEach(() => {
TestBed.configureTestingModule(
{
declarations: [CheckboxComponent],
},
);
fixture = TestBed.createComponent(CheckboxComponent);
comp = fixture.componentInstance;
input = fixture.debugElement.query(By.css('#mob_Q1')).nativeElement;
});
it('should click change value', () => {
expect(input.checked).toBeFalsy(); // default state
input.click();
fixture.detectChanges();
expect(input.checked).toBeTruthy(); // state after click
});
});
IS there a need to write fixture.detectChanges()?
I went through the same test without this and it ends with success.
Button 1 is 'checked' by default
const button1 = debugElement.nativeElement.querySelector(selectorBtn1);
const button2 = debugElement.nativeElement.querySelector(selectorBtn2);
...
expect(button1.checked).toBeTruthy();
expect(button2.checked).toBeFalsy();
button2.click();
expect(button1.checked).toBeFalsy();
expect(button2.checked).toBeTruthy();
...
ngModel directive is async one and requires to use asynchronous capabilities of Angular unit testing. Adding async and whenStable functions.
it('checkbox is checked if value is true', async(() => {
component.model = true;
fixture.detectChanges();
fixture.whenStable().then(() => {
const inEl = fixture.debugElement.query(By.css('#mob_Q1'));
expect(inEl.nativeElement.checked).toBe(true);
});
}));
Source LinkLink
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")
});
});
I used vue-cli to create sample project vue init webpack my-test3, and opted for including both e2e and unit tests.
Question 1: Based on documentation for template filters I tried to add new filter as such in main.js:
import Vue from 'vue'
import App from './App'
/* eslint-disable no-new */
new Vue({
el: '#app',
template: '<App/>',
components: { App },
filters: {
capitalize: function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
})
And my App.vue:
<template>
<div id="app">
<img src="./assets/logo.png">
<hello></hello>
<world></world>
<div class="test-test">{{ 'tesT' | capitalize }}</div>
</div>
</template>
<script>
import Hello from './components/Hello'
import World from './components/World'
export default {
name: 'app',
components: {
Hello,
World
}
}
</script>
I get warning(error):
[Vue warn]: Failed to resolve filter: capitalize (found in component <app>)
If I modify main.js to register filter before initializing new Vue app, then it works without problem.
import Vue from 'vue'
import App from './App'
Vue.filter('capitalize', function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})
/* eslint-disable no-new */
new Vue({
el: '#app',
template: '<App/>',
components: { App }
})
Why one works and other not?
Question 2: With example above that works Vue.filter(..., I added a test for World.vue component:
<template>
<div class="world">
<h1>{{ msg | capitalize }}</h1>
<h2>{{ desc }}</h2>
Items (<span>{{ items.length }}</span>)
<ul v-for="item in items">
<li>{{ item }}</li>
</ul>
</div>
</template>
<script>
export default {
name: 'world',
data () {
return {
msg: 'worldly App',
desc: 'This is world description.',
items: [ 'Mon', 'Wed', 'Fri', 'Sun' ]
}
}
}
</script>
And World.spec.js:
import Vue from 'vue'
import World from 'src/components/World'
Vue.filter('capitalize', function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})
describe('World.vue', () => {
it('should render correct title', () => {
const vm = new Vue({
el: document.createElement('div'),
render: (h) => h(World)
})
expect(vm.$el.querySelector('.world h1').textContent)
.to.equal('Worldly App')
})
it('should render correct description', () => {
const vm = new Vue({
el: document.createElement('div'),
render: (w) => w(World)
})
expect(vm.$el.querySelector('.world h2').textContent)
.to.equal('This is world description.')
})
})
For the above test to pass, I need to include Vue.filter(... definition for filter capitalize, otherwise tests would fail. So my question is, how to structure filters/components and initialize them, so testing is easier?
I feel like I should not have to register filters in unit tests, that should be part of the component initialization. But if component is inheriting/using filter defined from main app, testing component will not work.
Any suggestions, comments, reading materials?
A good practice is create a filters folder and define your filters inside this folder in individual files and define all your filters globally if you want to have access to them in all of your components, eg:
// capitalize.js
export default function capitalize(value) {
if (!value) return '';
value = value.toString().toLowerCase();
value = /\.\s/.test(value) ? value.split('. ') : [value];
value.forEach((part, index) => {
let firstLetter = 0;
part = part.trim();
firstLetter = part.split('').findIndex(letter => /[a-zA-Z]/.test(letter));
value[index] = part.split('');
value[index][firstLetter] = value[index][firstLetter].toUpperCase();
value[index] = value[index].join('');
});
return value.join('. ').trim();
}
To test it successfully and if you use #vue/test-utils, to test your single file components, you can do that with createLocalVue, like this:
// your-component.spec.js
import { createLocalVue, shallowMount } from '#vue/test-utils';
import capitalize from '../path/to/your/filter/capitalize.js';
import YOUR_COMPONENT from '../path/to/your/component.vue';
describe('Describe YOUR_COMPONENT component', () => {
const localVue = createLocalVue();
// this is the key line
localVue.filter('capitalize', capitalize);
const wrapper = shallowMount(YOUR_COMPONENT, {
// here you can define the required data and mocks
localVue,
propsData: {
yourProp: 'value of your prop',
},
mocks: {
// here you can define all your mocks
// like translate function and others.
},
});
it('passes the sanity check and creates a wrapper', () => {
expect(wrapper.isVueInstance()).toBe(true);
});
});
I hope to help you.
Regards.
My best guess is that if you define filters inside a component, they will be only available for use inside this component. In your case, you can only use capitalize in the main Vue instance, not its child components. Moving this to a global level solves the issue.
For the second question, you are doing the right thing by adding the filter definition in the testing file.
I would like to change the value of an input field from within an Angular 2 unit test.
<input type="text" class="form-control" [(ngModel)]="abc.value" />
I can't just change the ngModel because abc object is private:
private abc: Abc = new Abc();
In Angular 2 testing, can I simulate the user typing into the input field so that the ngModel will be updated with what the user has typed from within a unit test?
I can grab the DebugElement and the nativeElement of the input field without a problem. (Just setting a the value property on the nativeElement of the input field doesn't seem to work as it doesn't update the ngModel with what I've set for the value).
Maybe inputDebugEl.triggerEventHandler can be called, but I'm not sure what arguments to give it so it will simulate the user having typed a particular string of input.
You're right that you can't just set the input, you also need to dispatch the 'input' event. Here is a function I wrote earlier this evening to input text:
function sendInput(text: string) {
inputElement.value = text;
inputElement.dispatchEvent(new Event('input'));
fixture.detectChanges();
return fixture.whenStable();
}
Here fixture is the ComponentFixture and inputElement is the relevant HTTPInputElement from the fixture's nativeElement. This returns a promise, so you'll probably have to resolve it sendInput('whatever').then(...).
In context: https://github.com/textbook/known-for-web/blob/52c8aec4c2699c2f146a33c07786e1e32891c8b6/src/app/actor/actor.component.spec.ts#L134
Update:
We had some issues getting this to work in Angular 2.1, it didn't like creating a new Event(...), so instead we did:
import { dispatchEvent } from '#angular/platform-browser/testing/browser-util';
...
function sendInput(text: string) {
inputElement.value = text;
dispatchEvent(fixture.nativeElement, 'input');
fixture.detectChanges();
return fixture.whenStable();
}
The accepted solution didn't quite work for me in Angular 2.4. The value I had set was not appearing in the (test) UI, even after detectChanges() was called.
The way I got it to work was to set up my test as follows:
describe('TemplateComponent', function () {
let comp: TemplateComponent;
let fixture: ComponentFixture<TemplateComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [ FormsModule ],
declarations: [ TemplateComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TemplateComponent);
comp = fixture.componentInstance;
});
it('should allow us to set a bound input field', fakeAsync(() => {
setInputValue('#test2', 'Tommy');
expect(comp.personName).toEqual('Tommy');
}));
// must be called from within fakeAsync due to use of tick()
function setInputValue(selector: string, value: string) {
fixture.detectChanges();
tick();
let input = fixture.debugElement.query(By.css(selector)).nativeElement;
input.value = value;
input.dispatchEvent(new Event('input'));
tick();
}
});
My TemplateComponent component has a property named personName in this example, which was the model property I am binding to in my template:
<input id="test2" type="text" [(ngModel)]="personName" />
I also had trouble getting jonrsharpe's answer to work with Angular 2.4. I found that the calls to fixture.detectChanges() and fixture.whenStable() caused the form component to reset. It seems that some initialization function is still pending when the test starts. I solved this by adding extra calls to these methods before each test. Here is a snippet of my code:
beforeEach(() => {
TestBed.configureTestingModule({
// ...etc...
});
fixture = TestBed.createComponent(LoginComponent);
comp = fixture.componentInstance;
usernameBox = fixture.debugElement.query(By.css('input[name="username"]'));
passwordBox = fixture.debugElement.query(By.css('input[type="password"]'));
loginButton = fixture.debugElement.query(By.css('.btn-primary'));
formElement = fixture.debugElement.query(By.css('form'));
});
beforeEach(async(() => {
// The magic sauce!!
// Because this is in an async wrapper it will automatically wait
// for the call to whenStable() to complete
fixture.detectChanges();
fixture.whenStable();
}));
function sendInput(inputElement: any, text: string) {
inputElement.value = text;
inputElement.dispatchEvent(new Event('input'));
fixture.detectChanges();
return fixture.whenStable();
}
it('should log in correctly', async(() => {
sendInput(usernameBox.nativeElement, 'User1')
.then(() => {
return sendInput(passwordBox.nativeElement, 'Password1')
}).then(() => {
formElement.triggerEventHandler('submit', null);
fixture.detectChanges();
let spinner = fixture.debugElement.query(By.css('img'));
expect(Helper.isHidden(spinner)).toBeFalsy('Spinner should be visible');
// ...etc...
});
}));
How to check if select have some option in it after it has been updated by ajax function. My select component is like below
class Select extends React.component(){
constructor(){
super()
this.state.item=[]
}
componentWillMount(){
axios.get(list)
.then(function (response) {
this.setState({item:response.data})
// reponse data ['america','singapore','vietnam']
})
}
render(){
return(
<select>
this.state.item.map((i) => {
<option value="i">i</option>
})
</select>
)
}
}
Here is my try:
import {expect} from 'chai'
import {shallow} from 'enzyme'
import sinon from 'sinon'
import Select from 'Select'
describe('select list', () => {
it('should have options', () => {
const wrapper = shallow(<Select />)
wrapper.update()
const actual = wrapper.find('option').length
expect(actual).to.not.equal(0)
})
})
What wrong with this is, I got var actual = 0. It supposes to be 3. So I guess I missing something with axios. What should I add to my spec?
Your GET request might be still waiting for the response but mocha already has completed the test execution.
You could add a timeout and assert after a while and then call the done() callback when you are done with the test. Please take a look at the mocha's asynchronous code section.
describe('select list', () => {
it('should have options', (done) => {
const wrapper = shallow(<Select />)
wrapper.update()
setTimeout(() => {
const actual = wrapper.find('option').length
expect(actual).to.not.equal(0)
done();
}, 2000)
})
})
I recommend you to check the axios-mock-adapter which allows you to mock request rather than sending an actual request to the server.