I've some logic runs at onEnter/onLeave. I've used some setInterval at onEnter and clear them at onLeave using clearInterval.
How can I unit test above case?
<Route
component={App}
onEnter={onEnterApp}
onLeave={onLeaveApp}
path="/app">
Below is my test case but it fails ,
import React from 'react';
import App from '../components/views/app.jsx';
import {shallow, mount, render} from 'enzyme';
import {expect, assert} from 'chai';
import sinon from 'sinon';
import {mountWithIntl} from '../test_utils/intl-enzyme-test-helper.js';
describe(‘App renders correctly', () => {
it('When mounting set intervals', () => {
const wrapper = mountWithIntl(<App/>);
expect(window.x).to.exist;
expect(window.y).to.exist;
});
it('When unmounting clear intervals', () => {
const wrapper = mountWithIntl(<App/>);
wrapper.unmount();
expect(window.x).to.not.exist;
expect(window.y).to.not.exist;
});
});
The onEnter and onLeave props aren't tied to the componentWillMount and componentWillUnmount methods of your <App> component, so just mounting and unmounting the <App> will not call those functions.
Assuming you trust that React Router works, you can just test that your onEnterApp and onLeaveApp functions work properly
describe('onEnterApp', () => {
it('sets x and y', () => {
onEnterApp();
expect(global.x).to.exist;
expect(globa.y).to.exist;
});
});
If you want to verify that they are run when the URL matches the /app path, then you will need to involve a <Router>.
import createMemoryHistory from 'react-router';
describe('App', () => {
it('runs onEnterApp when navigating to /app', (done) => {
const history = createMemoryHistory(['/app']);
const holder = document.createElement('div');
render((
<Router history={history}>
<Route
component={App}
onEnter={onEnterApp}
onLeave={onLeaveApp}
path="/app">
</Router>
), holder, () => {
expect(global.x).to.exist;
expect(globa.y).to.exist;
done();
});
});
});
Testing onLeaveApp would require you to navigate to a new location with your history instance and then test that the desired global state exists.
history.push({ pathname: '/foo' })
Related
I am writing unit tests for VueJS components and have consulted the "Applying Global Plugins and Mixins" section of Vue Test Utils Common Tips. I have a component that depends on the Vuex store so it makes sense that I would transpose the example under that section for my purposes.
Here is my code for that component's specific .spec.js file:
import { createLocalVue, mount } from '#vue/test-utils'
import AppFooter from '#/components/AppFooter/AppFooter'
import store from '#/store'
describe('AppFooter component', () => {
const localVue = createLocalVue()
localVue.use(store)
it('AppFooter should have header slot', () => {
const AppFooterComponent = mount(AppFooter, {
localVue
})
/* TODO: Replace with a more appropriate assertion */
expect(true).toEqual(true)
})
})
This is pretty faithful to the example provided in the link above. However, the error I receive when I run the test suite is as follows:
Should I be installing the Vue store differently?
To elaborate on my comment, I believe it should look like the following, where you pass in the store on the mount() call.
import { createLocalVue, mount } from '#vue/test-utils'
import AppFooter from '#/components/AppFooter/AppFooter'
import Vuex from 'vuex'
import store from '#/store' //you could also mock this out.
describe('AppFooter component', () => {
const localVue = createLocalVue()
localVue.use(Vuex)
it('AppFooter should have header slot', () => {
const AppFooterComponent = mount(AppFooter, {
store,
localVue
})
/* TODO: Replace with a more appropriate assertion */
expect(true).toEqual(true)
})
})
I believe that you have something like this.$store.getters[someBeautifulGetterName] in you component. To make your tests mount the component your need to initialise store and pass it into your testing component. Just keep in mind that this would be a brand new instance of Vuex. Here is the code
import { shallowMount } from '#vue/test-utils'
import Vue from 'vue'
import Vuex from 'vuex'
import Tags from '#/components/Tags'
Vue.use(Vuex)
Vue.prototype.$store = new Vuex.Store()
const factory = (propsData) => {
return shallowMount(Tags, {
propsData: {
...propsData
}
})
}
describe('Tags', () => {
it("render tags with passed data", () => {
const wrapper = factory({ loading: true })
// TODO:
})
})
I'm trying to write a unit test for the logic within Vue's mounted() lifecycle hook, but not having much luck. The problem seems to be that mounted() never gets called when the component is mounted using vue-test-utils mount. Here's the Vue component I'm trying to test:
<template>
<div></div>
</template>
<script>
export default {
name: 'MyComponent',
mounted () {
this.$store.dispatch('logout')
}
}
</script>
And the test itself:
import { mount, createLocalVue } from '#vue/test-utils'
import Vuex from 'vuex'
import MyComponent from '#/components/MyComponent'
const localVue = createLocalVue()
localVue.use(Vuex)
describe('MyComponent.vue', () => {
let store
let actions
beforeEach(() => {
actions = {
logout: jest.fn().mockName('logout')
}
store = new Vuex.Store({
state: {},
actions
})
})
it('calls store "logout" action', () => {
mount(MyComponent, { localVue, store })
expect(actions.logout).toHaveBeenCalled()
})
})
However, this fails with expect(logout).toHaveBeenCalled() asserting false.
If I call the mocked store action directly with actions.logout() the test passes, and I have other tests which also call store actions on things like a button press, and those pass as well, so the problem definitely appears to be with the mounted() lifecycle hook.
Any thoughts?
(vue 2.5.4 and vue-test-utils 1.0.0-beta-.15)
Not sure how it's any different, but I abstracted the store mock to another file and everything seems to work now.
mocks.js
export const storeMock = Object.freeze({
state: {},
actions: {
logout: jest.fn().mockName('logout')
},
})
test.spec.js
import { shallowMount, createLocalVue } from '#vue/test-utils'
import Vuex from 'vuex'
import { storeMock } from './mocks.js'
import MyComponent from '#/components/MyComponent'
const localVue = createLocalVue()
localVue.use(Vuex)
describe('MyComponent.vue', () => {
let options
beforeEach(() => {
jest.clearAllMocks()
const store = new Vuex.Store(storeMock)
options = { store, localVue }
})
it('calls store "logout" action', () => {
shallowMount(MyComponent, options)
expect(storeMock.actions.logout).toHaveBeenCalled()
})
})
Without abstracting the store mock to another file, and slightly different approach without beforeEach (ruined my tests for some reason).
import { createLocalVue, shallowMount } from "#vue/test-utils";
import Vuex from "vuex";
import MyComponent from "#/components/MyComponent.vue";
describe("MyComponent", () => {
const localVue = createLocalVue();
localVue.use(Vuex);
const actions = {
logout: jest.fn()
};
const store = new Vuex.Store({ actions });
const wrapper = shallowMount(MyComponent, {
localVue,
store
});
it('calls store "logout" action', () => {
expect(actions.logout).toHaveBeenCalled();
});
});
I'm trying to mount a component that uses Vuex and requires $route.query to be mocked
import { mount, shallow, createLocalVue } from 'vue-test-utils'
import Vue from 'vue'
import expect from 'expect'
import Vuex from 'vuex'
import VueRouter from 'vue-router'
import Post from '../../components/Post.vue'
const localVue = createLocalVue()
localVue.use(Vuex);
localVue.use(VueRouter);
describe('Lead list', () => {
let wrapper;
let getters;
let store;
beforeEach(() => {
getters = {
post: () => { return {} }
}
store = new Vuex.Store({
getters
});
});
it('should just be true', () => {
const $route = {
path: '/some/path',
query: {}
}
wrapper = shallow(Post, {
localVue,
mocks: {
$route
}, store
});
expect(true).toBe(true);
});
});
And I'm getting back this error
TypeError: Cannot set property $route of #<VueComponent> which has only a getter
I've found the closed issue https://github.com/vuejs/vue-test-utils/issues/142 that has similar error. But my case is a little different. If I remove store or mocks from the options it works fine, but it does't work when you have both. Is this an issue or I'm doing something wrong?
Thanks
You're getting this error because you have installed VueRouter on the Vue constructor, by calling localVue.use(VueRouter). This adds $route as a read only property on the localVue constructor.
You're then trying to overwrite $router using mocks. mocks is unable to overwrite $route because it's been added as a read only property by Vue Router.
To fix your problem, you could create another localVue, install Vuex, and then use mocks to pass in $route:
it('should just be true', () => {
const freshLocalVue = createLocalVue()
freshLocalVue.use(Vuex)
const $route = {
path: '/some/path',
query: {}
}
wrapper = shallow(Post, {
localVue,
mocks: {
$route
},
store
})
expect(true).toBe(true)
})
I am using Enzyme to unit test my React components. I understand that in order to test the raw unconnected component I'd have to just export it and test it (I've done that). I have managed to write a test for the connected component but I am really not sure if this's the right way and also what exactly would I want to test for the connected component.
Container.jsx
import {connect} from 'react-redux';
import Login from './Login.jsx';
import * as loginActions from './login.actions';
const mapStateToProps = state => ({
auth: state.auth
});
const mapDispatchToProps = dispatch => ({
loginUser: credentials => dispatch(loginActions.loginUser(credentials))
});
export default connect(mapStateToProps, mapDispatchToProps)(Login);
Container.test.js
import React from 'react';
import {Provider} from 'react-redux';
import {mount, shallow} from 'enzyme';
import {expect} from 'chai';
import LoginContainer from '../../src/login/login.container';
import Login from '../../src/login/Login';
describe('Container Login', () => {
it('should render the container component', () => {
const storeFake = state => ({
default: () => {
},
subscribe: () => {
},
dispatch: () => {
},
getState: () => ({ ...state })
});
const store = storeFake({
auth: {
sport: 'BASKETBALL'
}
});
const wrapper = mount(
<Provider store={store}>
<LoginContainer />
</Provider>
);
expect(wrapper.find(LoginContainer).length).to.equal(1);
const container = wrapper.find(LoginContainer);
expect(container.find(Login).length).to.equal(1);
expect(container.find(Login).props().auth).to.eql({ sport: 'BASKETBALL' });
});
});
This is an interesting question.
I usually do import both container and component to do the testing. For container testing I use, redux-mock-store. Component testing is for testing async functions. For instance in your case, login process is an async function using sinon stubs. Here is a snippet of the same,
import React from 'react';
import {Provider} from 'react-redux';
import {mount, shallow} from 'enzyme';
import {expect} from 'chai';
import LoginContainer from '../../src/login/login.container';
import Login from '../../src/login/Login';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { stub } from 'sinon';
const mockStore = configureMockStore([thunk]);
describe('Container Login', () => {
let store;
beforeEach(() => {
store = mockStore({
auth: {
sport: 'BASKETBALL',
},
});
});
it('should render the container component', () => {
const wrapper = mount(
<Provider store={store}>
<LoginContainer />
</Provider>
);
expect(wrapper.find(LoginContainer).length).to.equal(1);
const container = wrapper.find(LoginContainer);
expect(container.find(Login).length).to.equal(1);
expect(container.find(Login).props().auth).to.eql({ sport: 'BASKETBALL' });
});
it('should perform login', () => {
const loginStub = stub().withArgs({
username: 'abcd',
password: '1234',
});
const wrapper = mount(<Login
loginUser={loginStub}
/>);
wrapper.find('button').simulate('click');
expect(loginStub.callCount).to.equal(1);
});
});
As you pointed out, the way I usually do this is to export the un-connected component as well, and test that.
i.e.
export {Login};
Here's an example. Source of the component, and source of the tests.
For the wrapped component, I don't author tests for those because my mappings (mapStateToProps and mapDispatchToProps) are generally very simple. If I wanted to test a wrapped component, I'd really just be testing those maps. So those are what I would choose to explicitly test, rather than re-testing the entire component in a wrapped form.
There are two ways to test those functions. One way would be to export the functions within the module itself.
i.e.;
export {mapStateToProps, mapDispatchToProps}
I'm not a huge fan of this, because I wouldn't want other modules in the app to access them. In my tests, I sometimes use babel-plugin-rewire to access "in-scope" variables, so that's what I would do in this situation.
That might look something like:
import {
Login, __Rewire__
}
const mapStateToProps = __Rewire__.__get__('mapStateToProps');
describe('mapStateToProps', () => { ... });
If we have a router issue, we can consider to add the router lib into the test file, eg:
import React from 'react';
import { Provider } from 'react-redux';
import { BrowserRouter as Router } from 'react-router-dom';
import { mount } from 'enzyme';
import ReadDots from './ReadDots';
const storeFake = state => ({
default: () => {
},
subscribe: () => {
},
dispatch: () => {
},
getState: () => ({ ...state })
});
const store = storeFake({
dot: {
dots: [
{
id: '1',
dot: 'test data',
cost: '100',
tag: 'pocket money'
}
]
}
});
describe('<ReadDots />', () => {
it('should render ReadDots component', () => {
const component = mount(
<Provider store={store}>
<Router>
<ReadDots />
</Router>
</Provider>
);
expect(component.length).toEqual(1);
});
});
Unit test works fine with angular2-seed project. The problem is it does not work with my project which resembles angular2-seed project.
Only difference between my project and angular2-seed is that I use gulpfile.ts without using tools directory. I defined all gulp tasks in a single file.
I need some help to give me some hint on this error.
I have this very simplified component
import {Component} from 'angular2/core';
import {RouterLink, Router} from 'angular2/router';
import {CORE_DIRECTIVES} from 'angular2/common';
#Component({
selector: 'cet-login',
moduleId: module.id,
templateUrl: 'apps/common/features/login/login.tpl.html',
directives: [RouterLink, CORE_DIRECTIVES]
})
export class LoginComponent {
constructor(
private router: Router
) {}
}
and I have very simple unit test
import {
TestComponentBuilder,
describe,
expect,
injectAsync,
it
} from 'angular2/testing';
import {Component} from 'angular2/core';
import {LoginComponent} from './login.cmp';
export function main(): void {
'use strict';
describe('Login component', () => {
it('should work',
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.createAsync(TestComponent)
.then(rootTC => {
rootTC.detectChanges();
expect(1).toBe(1);
});
}));
});
}
class Mock {}
#Component({
selector: 'test-cmp',
template: '<cet-login></cet-login>',
directives: [LoginComponent]
})
class TestComponent {}
I also have gulpfile.ts test section like the following;
gulp.task('test:buildjs', 'Compile typescript test files', ['test:buildcss'], () => {
var tsProject = tsProjectFn();
var result = gulp.src(PATH.src.ts)
.pipe(plumber())
.pipe(sourcemaps.init())
.pipe(inlineNg2Template({base: PATH.src.base}))
.pipe(tsc(tsProject));
return result.js
.pipe(sourcemaps.init())
.pipe(gulp.dest(PATH.dest.test));
});
gulp.task('test:unit', 'Start a karma server and run a unit test', (done: any) => {
return new karma.Server({
configFile: __dirname + '/karma.config.js',
singleRun: true
}).start(done);
});
When I run gulp test, which runs test:buildjs and test:unit, I have the following error
1) should work
Login component
Failed: Cannot read property 'componentInstance' of undefined
TypeError: Cannot read property 'componentInstance' of undefined
at new ComponentFixture_ (C:/repos/FAR/node_modules/angular2/src/testing/test_component_builder.js:38:51)
at C:/repos/FAR/node_modules/angular2/src/testing/test_component_builder.js:204:52
at Zone.run (C:/repos/FAR/node_modules/zone.js/dist/zone-microtask.js:1217:24)
at zoneBoundFn (C:/repos/FAR/node_modules/zone.js/dist/zone-microtask.js:1194:26)
at lib$es6$promise$$internal$$tryCatch (C:/repos/FAR/node_modules/zone.js/dist/zone-microtask.js:442:17)
at lib$es6$promise$$internal$$invokeCallback (C:/repos/FAR/node_modules/zone.js/dist/zone-microtask.js:454:18)
at lib$es6$promise$$internal$$publish (C:/repos/FAR/node_modules/zone.js/dist/zone-microtask.js:425:12)
at C:/repos/FAR/node_modules/zone.js/dist/zone-microtask.js:97:10
at Zone.run (C:/repos/FAR/node_modules/zone.js/dist/zone-microtask.js:1217:24)
at zoneBoundFn (C:/repos/FAR/node_modules/zone.js/dist/zone-microtask.js:1194:26)
at lib$es6$promise$asap$$flush (C:/repos/FAR/node_modules/zone.js/dist/zone-microtask.js:236:10)
karma.config.js is exactly the same as angualr2-seed
test-main.js is the exactly the same as angualr2-seed
Any idea what I miss and what I am doing wrong?
I don't know which version of Angular2 (in my case beta7+) you use but in my unit tests, I need to register TEST_BROWSER_PLATFORM_PROVIDERS and TEST_BROWSER_APPLICATION_PROVIDERS to be able to use the TestComponentBuilder class:
import {
it,
describe,
expect,
beforeEach,
inject,
injectAsync,
TestComponentBuilder,
ComponentFixture,
setBaseTestProviders
} from 'angular2/testing';
import {
TEST_BROWSER_PLATFORM_PROVIDERS,
TEST_BROWSER_APPLICATION_PROVIDERS
} from 'angular2/platform/testing/browser';
import {MyListComponent} from "./my-list";
import {MyService} from "../services/my-service";
describe('MyList Tests', () => {
setBaseTestProviders(TEST_BROWSER_PLATFORM_PROVIDERS,
TEST_BROWSER_APPLICATION_PROVIDERS);
let list:MyListComponent;
beforeEach(() => {
list = new MyListComponent();
});
it('should render list', injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.createAsync(MyListComponent).then((componentFixture: ComponentFixture) => {
const element = componentFixture.nativeElement;
componentFixture.detectChanges();
expect(element.querySelectorAll('li').length).toBe(3);
});
}));
});
See this plunkr: https://plnkr.co/edit/oVBPsH?p=preview.
Found the cause of this problem. The following line should be commented out for successful test.
import {enableProdMode} from 'angular2/core';
enableProdMode();