Angular2 unit test + creating object of an interface - unit-testing

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

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.

Electron + Jest - ipcRenderer is undefined in unit tests

I am contributing to a project which is built with React (with webpack) running in Electron. When executing unit tests with Jest, it fails with the error TypeError: Cannot read property 'on' of undefined (and works fine when not testing, eg. run with Electron).
The code:
import React, { Component } from 'react';
import { ipcRenderer } from 'electron';
// some more imports
class Setup extends Component {
constructor(props) {
super(props);
this.state = {
// some state
};
ipcRenderer.on('open-file-reply', this.someMethod); // << fails on this line
}
// more class stuff
}
It took me a few days but finally, I found this answer in this great blog post. Quote:
Jest is called from Node and doesn't run test code through Webpack.
Instead, we have to use Jest's mocking functions to replace the import
with a stub file.
Jest has a helper method called moduleNameMapper [object<string, string>] . From jest documentation:
A map from regular expressions to module names that allow to stub out
resources, like images or styles with a single module.
It should be added in your package.json root object like this:
{
"name": "My awesome app",
"jest": {
"moduleNameMapper": {
"electron": "<rootDir>/src/components/tests/mock/electron.js"
}
}
}
and the mock file itself (/src/components/tests/mock/electron.js):
export const ipcRenderer = {
on: jest.fn()
};
This way you can stub other electron modules and methods (like remote which is shown in the blog above).
Another way is creating an electron.js file in __mocks__ in your root folder.
The electron.js should look something like
export const ipcRenderer = {
on: jest.fn(),
};
You can read more at https://jestjs.io/docs/en/manual-mocks#mocking-node-modules

Unit test a typescript vue component

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)
})
})

Angular2 Component Unit Testing with Service Dependency

So I have this PromptComponent and the content will be updated by a service data.
Now I am trying to do the unit test this component which has this service dependency.
Here is the Component:
export class PromptComponent {
#Input() module: any;
#select() selectedPrompt;
constructor(private moduleService: ModuleService){}
}
Here is the service:
getQuote(): Promise<string> {
return new Promise(resolve => {
setTimeout( () => resolve(this.nextQuote()), 500 );
});
}
private nextQuote() {
if (this.next === quotes.length) { this.next = 0; }
return quotes[ this.next++ ];
}
Here is the unit test code:
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
PromptComponent,
],
providers: [ ModuleService]
});
fixture = TestBed.createComponent(PromptComponent);
component = fixture.componentInstance;
const moduleService = fixture.debugElement.injector.get(ModuleService);
spy = spyOn(moduleService, 'getQuote').and.returnValue(Promise.resolve(testService));
I referred to the official demo on the angular documentation
https://angular.io/resources/live-examples/testing/ts/eplnkr.html, as inside the TwainComponent.spec.
Something really weird is that as expected, inside the official demo, the TwainComponent test passed smoothly. But I, on the other hand, got this strange injectionError. If I remove the parameter inside the constructor, I am able to create the fixture instance but get injection error when it runs thorught the injector.get() part. But as soon as I put the private moduleService:ModuleService back inside the component constructor, I will get fixture undefined and also with the injection error.
So that means I cannot even execute my code to the actual service injection part (fixture.debugElement.injector.get(ModuleService)), I already got the error.
Does anybody have the same issue?
My code is almost identical to the demo code on Plunker. Have no clue how to move forward....

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);
});