Unit test a typescript vue component - unit-testing

I need to be able test my component (methods, computed properties, data, ...). However, when I import my vue component in my unit test:
import Pagination from 'src/components/shared/pagination.vue'
import { newComponent } from '../component-factory'
describe('pagination.vue', () => {
const propsData = {
metadata: {
page: 2,
records: 155,
total: 11,
},
perPage: 15,
}
it('should have correct number of records', () => {
const ctor = Vue.extend(Pagination)
const vm = new ctor({propsData}).$mount()
expect(vm.firstRecord).toBe(16)
expect(vm.lastRecord).toBe(30)
})
...
vm is of type Vue, and thus does not have the firstRecord/lastRecord properties. Running the test with karma shows a success, but the typescript compiler spits out Errors:
ERROR in ./tests/shared/pagination.spec.ts
(16,19): error TS2339: Property 'firstRecord' does not exist on type 'Vue'.
ERROR in ./tests/shared/pagination.spec.ts
(17,19): error TS2339: Property 'lastRecord' does not exist on type 'Vue'.
I tried casting:
...
const vm = new ctor({propsData}).$mount() as Pagination
...
But that results in a warning in VSCode:
[ts] Cannot find name 'Pagination'.
And has the effect of treating vm as type any which is totally counterproductive.
I think this all stems from the fact that when using .vue files you have to add the declaration:
declare module '*.vue' {
import Vue from 'vue'
export default typeof Vue
}
Which clearly sets the type of all .vue files to Vue, which isn't exactly a lie, but isn't helpful either... Any suggestions? What am I doing wrong?
For future reference, I have attempted to use vuetype which generates .d.ts files for each .vue file, but ran into this issue. Also, there is a request to make .vue a first class citizen in the typescript ecosystem, which would eliminate this problem. And, I just added a request for a vue language service extension

Up until Vue 2.5, their TypeScript documentation page recommended exporting an interface that extends Vue if you were not going to use vue-class-component. You can export this interface to use in your tests, to cast your component instance. The recommendation has been removed from the docs, but I have not been able to figure out how to change my tests to not need the interface.
It looks like vuetype could generate these interfaces for you, but I've just been creating them manually.
Here is a greatly simplified example, but you can define anything in your interface that you would reference on vm, ie data, props, methods:
// NOTE: Make sure your interface extends `Vue`!
export interface PaginationComponent extends Vue {
firstRecord: number,
lastRecord: number
}
export default {
name: 'Pagination',
data: function() {
return {
firstRecord: 16,
lastRecord: 30,
}
}
}
For your test, you can cast the component instance to the type of your exported interface:
import Pagination, {PaginationComponent} from 'src/components/shared/pagination.vue'
describe('pagination', () => {
it('should know about component data fields', () => {
const ctor = Vue.extend(Pagination)
const vm : PaginationComponent = new ctor().$mount()
expect(vm.firstRecord).toBe(16)
expect(vm.lastRecord).toBe(30)
})
})

Related

Aurelia testing components with transient dependencies never uses a mock

We are using the aurelia component testing as defined here (with jest): https://aurelia.io/docs/testing/components#testing-a-custom-element
The component we are testing has a transient dependency. We are creating a mock for this dependency but when we run the tests using au jest, the real one always gets injected by the DI container and never the mock.
Here is the Transient service:
import { transient } from "aurelia-framework";
#transient()
export class ItemService {
constructor() {
}
getItems(): void {
console.log('real item service');
}
}
Here is the 'Mock' service (we have also tried using jest mocks but we get the same result):
import { transient } from "aurelia-dependency-injection";
#transient()
export class MockItemService{
getItems():void {
console.log('mock item service');
}
}
Here is the component under test:
import {ItemService} from "../services/item-service";
import { autoinject } from "aurelia-dependency-injection";
#autoinject()
export class TestElement {
constructor(private _itemService: ItemService) {
}
attached(): void {
this._itemService.getItems();
}
}
Here is the spec file:
import {TestElement} from "../../src/resources/elements/test-element";
import {ComponentTester, StageComponent} from "aurelia-testing";
import {ItemService} from "../../src/resources/services/item-service";
import {MockItemService} from "./mock-item-service";
import {bootstrap} from "aurelia-bootstrapper";
describe('test element', () => {
let testElement;
const path: string = '../../src/resources/elements/test-element';
beforeEach(() => {
testElement = StageComponent.withResources(path).inView(`<test-element></test-element>`);
testElement.bootstrap(aurelia => {
aurelia.use.standardConfiguration();
aurelia.container.registerTransient(ItemService, MockItemService);
});
});
afterEach(() => {
testElement.dispose();
});
it('should call mock item service', async() => {
await testElement.create(bootstrap);
expect(testElement).toBeTruthy();
})
});
But every-time the test is run, the console logs out the real service and not the mock. I have traced this to the aurelia-dependency-injection.js in the Container.prototype.get function. The issue seems to be around this section of code:
var registration = aureliaMetadata.metadata.get(aureliaMetadata.metadata.registration, key);
if (registration === undefined) {
return this.parent._get(key);
}
The registration object seems to be a bit odd, if it was undefined, the code would work as the correct dependency is registered on the parent and it would get the mock dependency. However, it is not undefined therefore it registers the real service in the DI container on this line:
return registration.registerResolver(this, key, key).get(this, key);
The registration object looks like this:
registration = TransientRegistration {_key = undefined}
Is this a bug in aurelia or is there something wrong with what I am doing?
Many Thanks
p.s. GitHub repo here to replicate the issue: https://github.com/Magrangs/aurelia-transient-dependency-issue
p.p.s Forked the DI container repo and added a quick fix which would fix my particular issue but not sure what the knock on effects would be. If a member of the aurelia team could check, that would be good:
https://github.com/Magrangs/dependency-injection/commit/56c7d96a496e76f330a1fc3f9c4d62700b9ed596
After talking to Rob Eisenberg on the issue there is a workaround for this problem. Firstly remove the #transient decorator on the class and then in your app start (usually main.ts) register the class there as a transient.
See the thread here:
https://github.com/Magrangs/dependency-injection/commit/56c7d96a496e76f330a1fc3f9c4d62700b9ed596
I have also updated the repo posted above: https://github.com/Magrangs/aurelia-transient-dependency-issue
to include the fix.
Hopefully this will help any other devs facing the same issue.

How do you test Vuejs Components when using Vuex

I am writing unit tests for my vuejs components in a larger application. My application state is all in the vuex store, so almost all of my components pull data from vuex.
I can't find a working example for writing unit test for this. I have found
Where Evan You says:
When unit testing a component in isolation, you can inject a mocked store directly into the component using the store option.
But I can't find a good example of how to test these components. I have tried a bunch of ways. Below is the only way I have seen people do it. stackoverflow question and answer
Basically it looks like the
test.vue:
<template>
<div>test<div>
</template>
<script>
<script>
export default {
name: 'test',
computed: {
name(){
return this.$store.state.test;
}
}
}
</script>
test.spec.js
import ...
const store = new Vuex.Store({
state: {
test: 3
}
});
describe('test.vue', () => {
it('should get test from store', () => {
const parent = new Vue({
template: '<div><test ref="test"></test></div>',
components: { test },
store: store
}).$mount();
expect(parent.$refs.test.name).toBe(3);
});
}
Note the "ref" this example doesn't work without it.
Is this really the right way to do this? It seems like it will get messy fast, because it requires adding the props into the template as a string.
Evan's quote seems to imply that the store can be added directly to the child component (ie not the parent like the example).
How do you do that?
The answer is actually really straightforward but not currently documented.
const propsData = { prop: { id: 1} };
const store = new Vuex.Store({state, getters});
const Constructor = Vue.extend(importedComponent);
const component = new Constructor({ propsData, store });
Note the store passed to the constructor. propsData is currently documented, the "store" option isn't.
Also if you are using Laravel, there are weird problems with the webpack versions you may be running.
The
[vuex] must call Vue.use(Vuex),
Error was caused by useing laravel-elixir-webpack-official.
If you did the fix:
npm install webpack#2.1.0-beta.22 --save-dev
for this https://github.com/JeffreyWay/laravel-elixir-webpack-official/issues/17
Your tests that include Vuex seem to break with the
[vuex] must call Vue.use(Vuex)
even when you have Vue.use(Vuex)

Angular 2 Testing a service which injects Http without using Testbed

I want to test a simple Angular 2 data service. The service uses Http, but nothing else. In the quickstart guide it says:
However, it's often more productive to explore the inner logic of
application classes with isolated unit tests that don't depend upon
Angular. Such tests are often smaller and easier to read, write, and
maintain.
The example of writing an isolated unit test it gives is that for simple services you can just test the service by creating a new instance of it in each test... maybe something like:
beforeEach(() => { service = new EventDataService(); });
it('#getEvents should return an observable', () => {
expect(service.getEvents()).toBe(Observable.from([]);
});
However, my EventDataService uses Http, so I get an error if I don't put Http in the constructor like so:
beforeEach(() => { service = new EventDataService(http: Http); });
But Http doesn't exist unless I import it, which I don't want to do - I don't want to test Http. I tried stubbing http out, but all the ways I tried ended up failing or leading me to import even MORE things to satisfy the Typescript gods...
I'm sure I'm over thinking this. I have tried the suggestions on quite a few sites that talk about testing in Angular 2, but anything older than a few months is suspect to me since the framework has changed so much in the last 6-12 months. I feel like I should be able to keep this simple for such a simple example.
Am I doing something obvious wrong?
I am using Angular 2 V 2.4.10, Webpack 2.3.1, Sinon 2.1.0, and Typescript 2.2.1.
Service:
import { Injectable } from "#angular/core";
import { Http } from "#angular/http";
import { Observable } from "rxjs";
import { Event } from "../event/event.interface";
#Injectable()
export class EventDataService {
events: Event[];
constructor(private http: Http) { }
getEvents(): Observable<Event[]> {
return this.http.get("api/events")
.map((response) => {return response.json(); })
}
};
Spec:
import { Http } from "#angular/http";
import { EventDataService } from "./event-data.service";
import * as sinon from "sinon";
import { expect } from "chai";
describe("Event Data Service", () => {
it("GetEvents", () => {
sinon.stub(Http, "get").returns(Promise.resolve("sinon Event!"));
let eventDataService = new EventDataService();
expect(eventDataService.getEvents()).to.equal("sinon event!");;
});
});
Thank you!
Although i wholeheartedly agree with the general sentiment that you should use TestBed in this scenario to stub out your Http dependency (after all, that's a huge motivator for why Angular has dependency injection in the first place), I'm seeing some errors in your approach which seems to indicate some misunderstanding.
Your EventDataService has a constructor which expects a single parameter of type Http. Therefore, whenever you want to create an instance of your EventDataService manually, you have to use the constructor and pass a single parameter of type Http.
So, you should be doing:
let dataService = new EventDataService(x);
where x is a variable of type Http. What may not be obvious is that Typescript is not a language like Java - Typescript can get out of your way if you want it to. So, you could, for eg, just create a new object, and say it's of type 'Http' and the Typescript compiler will assume you know what you're doing and let you proceed.
So you could do:
let x = ({ } as Http);
let dataService = new EventDataService(x);
The first line is telling the compiler that i want the type of {} to be Http, and Typescript will get out of your way and assume you know what you're doing.
So, you can use that technique to get an 'instance' of Http for your testing - i use the word 'instance' very loosely.
However, if you look at your EventDataService, it expects that the http object that gets passed to its constructor to have a get method that returns an object that you can call map on. In other words, it expects get to return an Observable. So, if you want to fake out Http for testing purposes, your fake Http instance needs to have a get method, and that get method needs to return an Observable.
Putting all of the above together, if i wanted to write my own test without using TestBed, I'd end up with:
let fakeHttp = {
get: (_: any) => {}
};
// I'm not familiar with sinon,
// but i believe this is stubbing the get method of fakeHttp
// and returning a canned response
sinon.stub(fakeHttp, "get").returns(Observable.of("sinon Event!"));
let eventDataService = new EventDataService(fakeHttp);
// remember, getEvents returns an observable,
// so to test it you have to subscribe to it and check its values
eventDataService.getEvents().subscribe(data => {
expect(data).to.equal("sinon Event!");
});
Would i approach this this way? Probably not - I'd just use TestBed to get a fake Http instance (not because this approach is that difficult, but just because TestBed simplifies things when you have multiple dependencies, and services always seem to grow that way). But at the end of the day, constructor dependency injection, like what Angular uses, is pretty easy to understand - pass your dependencies (fake or real) as parameters to the constructor of the class you're trying to create an instance of.
You should need to create a testing module and mock the response to test the service methods provide dependencies in your testing module
import { async, getTestBed, TestBed, inject } from "#angular/core/testing";
import { Response, ResponseOptions, HttpModule, XHRBackend } from "#angular/http";
import { MockBackend, MockConnection } from "#angular/http/testing";
import { EventDataService } from "./event-data.service";
describe("EventDataService", () => {
let mockBackend: MockBackend;
let service: EventDataService;
let injector: Injector;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpModule],
providers: [
{ provide: XHRBackend, useClass: MockBackend },
EventDataService
]
});
injector = getTestBed();
});
beforeEach(() => {
mockBackend = injector.get(XHRBackend);
service = injector.get(EventDataService);
});

Angular2 unit test + creating object of an interface

Little exhausted here, may be that is why my title is not so accurate.
I am writing a unit test for my DummyService:
import {Injectable} from '#angular/core';
#Injectable()
export class DummyService {
getAllDataSources():Promise<Array<DummyData>> {
return new Promise<DummyData[]>(resolve =>
setTimeout(()=>resolve([]), 1000) // 1 seconds
);
}
}
Please assume am returning a list of DummyData objects from getAllDataSources.
Now, I have a structure/interface for the dummy data in the same service file:
export interface DummyData{
Name:string;
IsActive:boolean;
}
I tried to write unit test for this service:
import {DummyService, DummyData} from './dummy.service';
import {
beforeEachProviders
} from '#angular/core/testing';
import {provide} from '#angular/core';
export function main() {
describe('dummy.service', () => {
let dsService:DummyService;
it('should fetch data', ()=> {
dummyData: DummyData = new DummyData(); // >>>> culprit...
expect(1).toEqual(1);
});
});
}
This unit test seem little funny, as I am really not calling DummyServices function to get the list of DummyData.
I am doing this because I was getting some issue, due to which I was not able to see my test. I did some research, spent a whole day and finally found that this structure DummyData is the CULPRIT. I proved this to myself when I tried creating an object of it in my unit test (in the code above) and I got the following error:
FAILED TESTS:
dummy.service
✖ should fetch data
PhantomJS 2.1.1 (Linux 0.0.0)
Chrome 50.0.2661 (Linux 0.0.0)
ReferenceError: **DummyData is not defined**
at eval (/home/aodev/WebstormProjects/Data Federation App/data-mapping-app/dist/dev/app/shared/datasource.service.spec.js:8:28)
at Object.eval (/home/aodev/WebstormProjects/Data Federation App/data-mapping-app/node_modules/#angular/core/testing/testing.js:80:25)
So, can someone tell me please, what am I doing wrong?
Why I cannot create the object of DummyData inside my unit test?
Please help!
TypeScript interfaces exist only during compile time, runtime knows nothing about interfaces.
This is how you create instance that implements interface:
interface itest
{
success:boolean;
}
let a:itest = {success: true}; //compiler checks that object matches interface itest

Global variable in Jest Unit Test

I am new to Jest and trying to write some unit tests for my existing React App. I have a global variable window.CONFIG which stores some configurations which are used at different places in app. This variable is initialised in a script tag of landing HTML page
Now I am trying to write a test of an helper function which depends on this window.CONFIG and it is always undefined when accesses
Here is the code:
config.js
export default window.CONFIG;
app/helper.js
import config from "../config";
export default {
getCompanyURL(company) {
return config.baseUrl + "/companies/" + company.id;
},
}
_ tests _/helpers-test.js
jest.dontMock('../app/helpers.js');
var helper = require('../app/helpers.js').default;
describe('Get company URL', function() {
it('returns company url with company id appended', function() {
expect(companies.getCompanyURL({id: 1})).toBe('test_base_url/companies/1');
});
});
config for Get Company Url is always undefined. As the browser landing page is not loaded window.CONFIG is not initialised. How can I mock this config module in my unit test in Jest?
Thanks in advance!!
I'm not sure if this helps you or not but you can put global variables directly into your package.json
"jest":{
"globals": {
"config": {"foo":"bar"}
}
}
In a recent project (using create-react-app, jest, react testing library and TypeScript), I was using the variable globalThis._env_.
All my attempts to override it in the files containing the tests failed, but adding it to setupTests.ts worked in the end:
// in setuTests.ts
globalThis._env_ = {
UPLOAD_FILE_SIZE_LIMIT_MB: '5'
};
(I think setupTests.js / setupTests.ts is a react testing library thing - https://create-react-app.dev/docs/running-tests/#react-testing-library )
I solved this by creating a manual mock of config.js in __mocks__ directory as follows:
let configMock = jest.genMockFromModule('../config');
let __mockConfig = {};
__mockConfig.baseUrl = "test_base_url/";
configMock = __mockConfig;
export default configMock;