I've been looking for a good pattern to unit test routes I have configured in my application so that I know the specified modules exist on disk as defined.
Here is an example route configuration:
import { Aurelia, PLATFORM } from 'aurelia-framework';
import { Router, RouterConfiguration } from 'aurelia-router';
export class App {
params = new bindParameters();
router: Router;
configureRouter(config: RouterConfiguration, router: Router) {
config.title = 'Aurelia';
config.map([{
route: ['', 'home'],
name: 'home',
settings: { icon: 'home' },
moduleId: PLATFORM.moduleName('../home/home'),
nav: true,
title: 'Home'
}, {
route: 'sample',
name: 'sample',
settings: { icon: 'education' },
moduleId: PLATFORM.moduleName('../sample/index'),
nav: true,
title: 'Sample Information'
}]);
this.router = router;
}
}
class bindParameters {
user = "user_name";
}
To test it I took the approach of passing in an instance of a router then checking to see if it exists:
import { App } from './app';
import jasmine from 'jasmine';
import { Container } from "aurelia-framework";
import { RouterConfiguration, Router } from "aurelia-router";
describe('application routes', function () {
let app: App;
let router: Router;
let routerConfiguration: RouterConfiguration;
let configureRouter: Promise<void>;
beforeEach(() => {
var container = new Container().makeGlobal();
routerConfiguration = container.get(RouterConfiguration);
router = container.get(Router);
app = new App();
app.configureRouter(routerConfiguration, router);
configureRouter = router.configure(routerConfiguration);
routerConfiguration.exportToRouter(router);
});
it('should exist for sample', function () {
expect(router).not.toBeNull();
//configureRouter.then(function () {
//var route = router.routes.find((route) => route.name == 'sample');
// add some assert that the sample module can be found
// done();
//});
});
});
My current problem is that the container is returning a null Router as shown by the current test. The closest pattern I have found to what I am trying to do is in this question.
What am I missing in my example test and also is there a better way to test route configuration?
It seems #thinkOfaNumber was right. Turns out my test was good, but I was missing reflect-metadata. When I applied the fix outlined in this stackoverflow post my tests passed.
Related
I'm testing a Single file component that uses vue router to watch $route. The problem is that I can't get the test to both change the route and trigger the watcher's function.
The test file:
import { createLocalVue, shallow } from 'vue-test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
const localVue = createLocalVue();
localVue.use(Vuex);
const $route = {
path: '/my/path',
query: { uuid: 'abc' },
}
wrapper = shallow({
localVue,
store,
mocks: {
$route,
}
});
it('should call action when route changes', () => {
// ensure jest has a clean state for this mocked func
expect(actions['myVuexAction']).not.toHaveBeenCalled();
vm.$set($route.query, 'uuid', 'def');
//vm.$router.replace(/my/path?uuid=def') // tried when installing actual router
//vm.$route.query.uuid = 'def'; // tried
//vm.$route = { query: { uuid: 'def'} }; // tried
expect(actions['myVuexAction']).toHaveBeenLastCalledWith({ key: true });
});
My watch method in the SFC:
watch: {
$route() {
this.myVuexAction({ key: true });
},
},
How do you mock router in such a way that you can watch it and test the watch method is working as you expect?
This is how I'm testing a watch on route change that adds the current route name as a css class to my app component:
import VueRouter from 'vue-router'
import { shallowMount, createLocalVue } from '#vue/test-utils'
import MyApp from './MyApp'
describe('MyApp', () => {
it('adds current route name to css classes on route change', () => {
// arrange
const localVue = createLocalVue()
localVue.use(VueRouter)
const router = new VueRouter({ routes: [{path: '/my-new-route', name: 'my-new-route'}] })
const wrapper = shallowMount(MyApp, { localVue, router })
// act
router.push({ name: 'my-new-route' })
// assert
expect(wrapper.find('.my-app').classes()).toContain('my-new-route')
})
})
Tested with vue#2.6.11 and vue-router#3.1.3.
I checked how VueRouter initializes $route and $router and replicated this in my test. The following works without using VueRouter directly:
const localVue = createLocalVue();
// Mock $route
const $routeWrapper = {
$route: null,
};
localVue.util.defineReactive($routeWrapper, '$route', {
params: {
step,
},
});
Object.defineProperty(localVue.prototype, '$route', {
get() { return $routeWrapper.$route; },
});
// Mock $router
const $routerPushStub = sinon.stub();
localVue.prototype.$router = { push: $routerPushStub };
const wrapper = shallowMount(TestComponent, {
localVue,
});
Updating $route should always be done by replacing the whole object, that is the only way it works without using a deep watcher on $route and is also the way VueRouter behaves:
$routeWrapper.$route = { params: { step: 1 } };
await vm.wrapper.$nextTick();
Source: install.js
Its working for me
let $route = {
name: 'any-route',
};
We defined a $route and we called like
wrapper = mount(YourComponent, {
mocks: {
$route,
},
});
and my componente is like this
#Watch('$route', { deep: true, immediate: true, })
async onRouteChange(val: Route) {
if (val.name === 'my-route') {
await this.getDocumentByUrl();
await this.allDocuments();
}
};
pd: I use typescript, but this work with the another format
and finally my test
it('my test', ()=>{
const getDocumentByUrl = jest.spyOn(wrapper.vm, 'getDocumentByUrl');
const allDocuments = jest.spyOn(wrapper.vm, 'allDocuments');
wrapper.vm.$route.name = 'my-route';
await flushPromises();
expect(getDocumentByUrl).toHaveBeenCalled();
expect(allDocuments).toHaveBeenCalled();
})
The way to do this actually is to use vue-test-utils wrapper method, setData.
wrapper.setData({ $route: { query: { uuid: 'def'} } });
Is there any way to unit test the navigation guards in a router file ?
Could not find any post or link on this topic ... ant tips, trick or feedback welcome..
Here is the router/index.js , and I would like to test the router.beforeEach()
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '#/pages/HomePage'
import Login from '#/pages/LoginPage'
import ShoppingLists from '#/pages/ShoppingListsPage'
import vueAuthInstance from '../services/auth.js'
Vue.use(VueRouter)
const router = new VueRouter({
mode: 'history',
routes: [
{
path: '/',
name: 'home',
component: Home,
meta: { auth: false, title: 'Home' }
},
{
path: '/login',
name: 'login',
component: Login,
meta: { auth: false, title: 'Login' }
},
{
path: '/shoppinglists',
name: 'shoppinglists',
component: ShoppingLists,
meta: { auth: true, title: 'Shopping Lists' }
},
{
path: '/logout',
name: 'logout'
}
]
})
router.beforeEach(function (to, from, next) {
if (to.meta && to.meta.title) {
document.title = to.meta.title
}
if (to.meta && to.meta.auth !== undefined) {
if (to.meta.auth) {
if (vueAuthInstance.isAuthenticated()) {
next()
} else {
router.push({ name: 'login' })
}
} else {
next()
}
} else {
next()
}
})
export default router
I found a way to do it, importing the router and using simply router.push) to navigate. I also need to stub the vueAuthInstance to authenticate or not the request
import VueRouter from 'vue-router'
import Vue from 'vue'
import sinon from 'sinon'
import router from '#/router/index'
import vueAuthInstance from '#/services/auth.js'
Vue.use(VueRouter)
describe('Router', () => {
let sandbox
beforeEach(() => {
sandbox = sinon.sandbox.create()
router
})
afterEach(() => {
sandbox.restore()
})
it('should be in history mode', () => {
sandbox.stub(vueAuthInstance, 'isAuthenticated').returns(false)
expect(router.mode).to.eql('history')
})
it('should be able to navigate without authentication', () => {
sandbox.stub(vueAuthInstance, 'isAuthenticated').returns(false)
router.push('/')
expect(router.history.current.path).to.eql('/')
expect(router.getMatchedComponents('/')[0].name).to.eql('HomePage')
router.push('/login')
expect(router.history.current.path).to.eql('/login')
expect(router.getMatchedComponents('/login')[0].name).to.eql('LoginPage')
})
it('should not be able to navigate to protected page when not authenticated', () => {
sandbox.stub(vueAuthInstance, 'isAuthenticated').returns(false)
router.push('/shoppinglists')
expect(router.history.current.path).to.eql('/login')
expect(router.getMatchedComponents('/login')[0].name).to.eql('LoginPage')
})
it('should be able to navigate to protected page when authenticated', () => {
sandbox.stub(vueAuthInstance, 'isAuthenticated').returns(true)
router.push('/shoppinglists')
expect(router.history.current.path).to.eql('/shoppinglists')
expect(router.getMatchedComponents('/shoppinglists')[0].name).to.eql('ShoppingListPage')
})
it('should be able to navigate to unprotected page when authenticated', () => {
sandbox.stub(vueAuthInstance, 'isAuthenticated').returns(true)
router.push('/home')
expect(router.history.current.path).to.eql('/home')
expect(router.getMatchedComponents('/')[0].name).to.eql('HomePage')
})
})
I am using ng2-file-upload. How do I mock its FileUploader class in unit testing?
import { FileUploader } from 'ng2-file-upload/ng2-file-upload';
#Component({
selector: 'my-component',
template: '...',
providers: [ MyService ]
})
export class MyComponent {
public uploader: FileUploader = new FileUploader({url: '/my-app/api/upload',
authToken: 'token'});
constructor() {
this.uploader.onCompleteItem = (item:any, response: any, headers: any) => {
console.log('how to test here');
}
}
I am having a hard time mocking it in my spec. Please help.
I was going to reply to your comment, but I think the following may help to answer your original question on how to mock the FileUploader class taken from their unit test from the file file-drop.directive.spec.ts:
import { Component } from '#angular/core';
import { inject, ComponentFixture, TestBed } from '#angular/core/testing';
import { FileUploader } from './file-uploader.class';
import { FileUploadModule } from './file-upload.module';
#Component({
selector: 'container',
template: `<input type="file" ng2FileSelect [uploader]="uploader" />`
})
export class ContainerComponent {
public uploader:FileUploader = new FileUploader({url: 'localhost:3000'});
}
describe('Directive: FileSelectDirective', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [FileUploadModule],
declarations: [ContainerComponent],
providers: [ContainerComponent]
});
});
it('should be fine', inject([ContainerComponent], (fixture:ComponentFixture<ContainerComponent>) => {
expect(fixture).not.toBeNull();
}));
});
Where import { FileUploader } from './file-uploader.class'; is how you import the FileUploader into your test, ContainerComponent is imported into the test itself.
Further to this, I have created a dummy file to test with on my component, but I am still writing it!
it('should accept a file for upload', () => {
var modifiedDate = new Date();
var file = new File([3555], 'test-file.jpg', {lastModified : modifiedDate, type: 'image/jpeg'});
FileUploadComponent.upload(file);
});
To test if this works, I have a metadata model that I expect to be populated upon the file being selected. I can therefore make two assertions, that both the upload input box will have a file and that the metadata object will be populated.
In the case of ng2-file-upload, I beleive the file list will be populated allowing you to check that the test file has been imported into that.
Good luck!
I have a question about the way I've seen (the very few) examples of testing of data passed down from a parent component into a child component. Currently, in the Angular2 docs, they're testing to see if data has been passed down from a parent component to a child by inspecting the dom values of the child component. The issue that I have with this approach is that it forces the parent's spec to know the html structure of the child component. The parent component's job is just to pass data into the child. An example...
I have a Story Component as follows:
'use strict';
import {Component, OnInit, Input} from '#angular/core';
import {StoryService} from '../../services/story.service';
import {StoryModel} from '../../models/story-model';
import {AlbumCover} from './album-cover/album-cover';
import {Author} from "./author/author";
import {StoryDuration} from "./story-duration/story-duration";
#Component({
selector: 'story',
templateUrl: 'build/components/story/story.html',
providers: [StoryService],
directives: [AlbumCover, Author, StoryDuration]
})
export class Story implements OnInit {
#Input('id') id:number;
public story:StoryModel;
constructor(private storyService:StoryService) {}
ngOnInit() {
this.getStory();
}
private getStory() {
this.storyService.getStory(this.id).subscribe(story => this.story = story);
}
}
Notice how it has an AlbumCover Component dependency in the directives array in the #Component decorator.
Here is my Story template:
<div *ngIf="story">
<album-cover [image]="story.albumCover" [title]="story.title"></album-cover>
<div class="author-duration-container">
<author [avatar]="story.author.avatar" [name]="story.author.name"></author>
<story-duration [word-count]="story.wordCount"></story-duration>
</div>
</div>
Notice the <album-cover [image]="story.albumCover" [title]="story.title"></album-cover> line where I'm binding the story.albumCover from the Story controller to the image property of the AlbumCover. This is all working perfectly. Now for the test:
import {provide} from '#angular/core';
import {beforeEach, beforeEachProviders, describe, expect, injectAsync, it, setBaseTestProviders, resetBaseTestProviders} from '#angular/core/testing';
import {HTTP_PROVIDERS} from '#angular/http';
import {BROWSER_APP_DYNAMIC_PROVIDERS} from "#angular/platform-browser-dynamic";
import {TEST_BROWSER_STATIC_PLATFORM_PROVIDERS, ADDITIONAL_TEST_BROWSER_PROVIDERS} from '#angular/platform-browser/testing';
import {ComponentFixture, TestComponentBuilder} from '#angular/compiler/testing';
import {Observable} from 'rxjs/Observable';
// TODO: this pattern of importing 'of' can probably go away once rxjs is fixed
// https://github.com/ReactiveX/rxjs/issues/1713
import 'rxjs/add/observable/of';
resetBaseTestProviders();
setBaseTestProviders(
TEST_BROWSER_STATIC_PLATFORM_PROVIDERS,
[BROWSER_APP_DYNAMIC_PROVIDERS, ADDITIONAL_TEST_BROWSER_PROVIDERS]
);
import {Story} from './story';
import {StoryModel} from '../../models/story-model';
import {StoryService} from '../../services/story.service';
var mockStory = {
id: 1,
title: 'Benefit',
albumCover: 'images/placeholders/story-4.jpg',
author: {
id: 2,
name: 'Brett Beach',
avatar: 'images/placeholders/author-1.jpg'
},
wordCount: 4340,
content: '<p>This is going to be a great book! I <strong>swear!</strong></p>'
};
class MockStoryService {
public getStory(id):Observable<StoryModel> {
return Observable.of(mockStory);
}
}
describe('Story', () => {
var storyFixture,
story,
storyEl;
beforeEachProviders(() => [
HTTP_PROVIDERS
]);
beforeEach(injectAsync([TestComponentBuilder], (tcb:TestComponentBuilder) => {
return tcb
.overrideProviders(Story, [
provide(StoryService, {
useClass: MockStoryService
})
])
.createAsync(Story)
.then((componentFixture:ComponentFixture<Story>) => {
storyFixture = componentFixture;
story = componentFixture.componentInstance;
storyEl = componentFixture.nativeElement;
componentFixture.detectChanges();
});
}));
describe(`ngOnInit`, () => {
describe(`storyService.getStory`, () => {
it(`should be called, and on success, set this.story`, () => {
spyOn(story.storyService, 'getStory').and.callThrough();
story.ngOnInit();
expect(story.storyService.getStory).toHaveBeenCalled();
expect(story.story.title).toBe('Benefit');
});
});
});
it('should not show the story component if story does not exist', () => {
story.story = null;
storyFixture.detectChanges();
expect(storyEl.children.length).toBe(0);
});
it('should show the story component if story exists', () => {
story.story = mockStory;
storyFixture.detectChanges();
expect(storyEl.children.length).not.toBe(0);
});
describe('story components', () => {
beforeEach(() => {
story.story = mockStory;
storyFixture.detectChanges();
});
describe('album cover', () => {
var element,
img;
beforeEach(() => {
element = storyEl.querySelector('album-cover');
img = element.querySelector('img');
});
it(`should be passed the story albumCover and title to the album cover component`, () => {
expect(img.attributes.src.value).toBe(mockStory.albumCover);
expect(img.attributes.alt.value).toBe(mockStory.title);
});
});
describe('author', () => {
var element,
img,
nameEl;
beforeEach(() => {
element = storyEl.querySelector('author');
img = element.querySelector('img');
nameEl = element.querySelector('.name');
});
it(`should be passed the author name and avatar`, () => {
expect(img.attributes.src.value).toBe(story.story.author.avatar);
expect(img.attributes.alt.value).toBe(story.story.author.name);
expect(nameEl.innerText).toBe(story.story.author.name);
});
});
describe('story duration', () => {
var element;
beforeEach(() => {
element = storyEl.querySelector('.story-duration');
});
it(`should be passed the word count to generate the total read time`, () => {
story.story.wordCount = 234234;
storyFixture.detectChanges();
expect(element.innerText).toBe(`852 min read`);
});
});
});
});
Look at my describe('album cover'.... The way I'm passing this expectation is that I'm finding the <album-cover> element, then finding the <img> tag inside of it, then checking the <img>'s DOM attributes. To me, this expection should be inside of the album-cover.spec.ts - NOT the story.spec.ts.
My question is: is there a way to test if a parent component passed data into a child component without relying on reading dom values?
You can use overrideTemplate to pass a view just for the test.
return tcb
.overrideTemplate(AlbumCover, '<div>{{valueFromParent}}</div>')
.overrideProviders(Story, [
I have a side-bar component which relies on side-bar service which is injected into it via initializer.
the component then has a computed property title which is tied to the same property on the service:
title: function () {
return this.get('sideBarService.title');
}.property('sideBarService.title'),
This works in the app itself but I cannot get the component to update in an integration test when the service is upated.
Here is my non working integration test:
import Ember from 'ember';
import startApp from '../helpers/start-app';
import hbs from 'htmlbars-inline-precompile';
import { moduleForComponent, test } from 'ember-qunit';
var application, container, sideBarService;
moduleForComponent('side-bar', 'Integration | side-bar',{
integration: true,
beforeEach: function() {
application = startApp();
container = application.__container__;
sideBarService = container.lookup('service:side-bar');
},
afterEach: function() {
Ember.run(application, 'destroy');
}
});
test('it displays the correct title', function(assert) {
assert.expect(1);
Ember.run(function () {
sideBarService.set('title', 'Hello');
});
this.render(hbs`
{{side-bar}}
`);
var content = this.$('.side-bar-content .title').text().trim();
var serviceTitle = sideBarService.get('title');
// fails
assert.deepEqual(content, serviceTitle);
});
Interestingly, if I debug in the test and grab the component with the console and then grab the sideBarService off of the component, it is aware of the updated title value and even the value title on the component itself seems to be updated but the dom never gets updated:
//debugged in browser console
var sb = container.lookup('component:side-bar')
undefined
sb.get('title')
"Hello"
sb.get('sideBarService.title')
"Hello"
this.$('.title').text().trim()
""
Is this a run loop issue? If so what do I need to do to set it off?
edit: In regards to Toran's comment. Does this look right?
var done = assert.async();
var content = this.$('.side-bar-content .title').text().trim();
var serviceTitle = sideBarService.get('title');
setTimeout(function() {
assert.deepEqual(content, serviceTitle);
done();
});
I would probably go about fixing this by avoiding the injection in the initializer and instead using the Ember.inject.service helper.
// component
import Ember from 'ember'
const { Component, inject, computed } = Ember;
const { service } = inject;
const { alias } = computed;
export default Component.extend({
sideBarService: service('side-bar'),
title: alias('sideBarService.title')
});
Then in your test, you can pass the service when you use the component.
import Ember from 'ember';
import startApp from '../helpers/start-app';
import hbs from 'htmlbars-inline-precompile';
import { moduleForComponent, test } from 'ember-qunit';
var application, container, sideBarService;
moduleForComponent('side-bar', 'Integration | side-bar',{
integration: true,
beforeEach: function() {
application = startApp();
},
afterEach: function() {
Ember.run(application, 'destroy');
}
});
test('it displays the correct title', function(assert) {
assert.expect(1);
this.set('sideBarService', Ember.Object.create({
title: 'hello'
}));
this.render(hbs`
{{side-bar sideBarService=sideBarService}}
`);
var title = this.$('.side-bar-content .title').text().trim();
assert.equal(title, 'hello'); // Hopefully passes
});