Aurelia testing components with transient dependencies never uses a mock - unit-testing

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.

Related

Mocking a Flow.js interface with Jest?

How might a Flow.js interface be mocked with Jest? To my surprise, I haven't found this issue addressed anywhere.
I'm fairly new to both, but the only (untested) option I see is to create a class that inherits from the interface and then mock the implementing class. This seems quite cumbersome and I don't believe I could place the implementing classes (which are what would actually be mocked) inside the __mocks__ folders expected by Jest and still get the expected behavior.
Any suggestions? Is there a more appropriate mocking tool?
Update
Why do I want to create a mock for an interface? This code intends to have a clean separation of the domain and implementation layers, with the domain classes using Flow interfaces for all injected dependencies. I want to test these domain classes. Using a mocking tool could ideally allow me to more easily and expressively modify the behavior of the mocked services and confirm that the domain class being tested is making the appropriate calls to these mocked services.
Here's a simplified example of a class that I would be testing in this scenario. UpdateResources would be the class under test, while ResourceServer and ResourceRepository are interfaces for services that I would like to mock and 'spy' upon:
// #flow
import type { ResourceServer } from '../ResourceServer';
import type { ResourceRepository } from '../ResourceRepository';
/**
* Use case for updating resources
*/
export default class UpdateResources {
resourceServer: ResourceServer;
resourceRepository: ResourceRepository;
constructor(resourceServer: ResourceServer, resourceRepository: ResourceRepository) {
this.resourceServer = resourceServer;
this.resourceRepository = resourceRepository;
}
async execute(): Promise<boolean> {
const updatesAvailable = await this.resourceServer.checkForUpdates();
if (updatesAvailable) {
const resources = await this.resourceServer.getResources();
await this.resourceRepository.saveAll(resources);
}
return updatesAvailable;
}
}
A solution
The approach I've arrived at which seems to work quite well for my purposes is to create a mock implementation of the interface in the __mocks__ directory what exposes jest.fn objects for all implemented methods. I then instantiate these mock implementations with new and skip any use of jest.mock().
__mocks__/MockResourceServer.js
import type { ResourceServer } from '../ResourceServer';
export default class MockResourceServer implements ResourceServer {
getResources = jest.fn(() => Promise.resolve({}));
checkForUpodates = jest.fn(() => Promise.resolve(true));
}
__mocks__/MockResourceRepository.js
import type { ResourceRepository } from '../ResourceRepository';
export default class MockResourceRepository implements ResourceRepository {
saveAll = jest.fn(() => Promise.resolve());
}
__tests__/UpdateResources.test.js
import UpdateResources from '../UpdateResources';
import MockResourceRepository from '../../__mocks__/MockResourceRepository';
import MockResourceServer from '../../__mocks__/MockResourceServer';
describe('UpdateResources', () => {
describe('execute()', () => {
const mockResourceServer = new MockResourceServer();
const mockResourceRepository = new MockResourceRepository();
beforeEach(() => {
jest.clearAllMocks();
});
it('should check the ResourceServer for updates', async () => {
const updateResources = new UpdateResources(mockResourceServer, mockResourceRepository);
await updateResources.execute();
expect(mockResourceServer.checkForUpdates).toHaveBeenCalledTimes(1);
});
it('should save to ResourceRepository if updates are available', async () => {
mockResourceServer.load.mockResolvedValue(true);
const updateResources = new UpdateResources(mockResourceServer, mockResourceRepository);
await updateResources.execute();
expect(mockResourceRepository.saveAll).toHaveBeenCalledTimes(1);
});
it('should NOT save to ResourceRepository if NO updates are available', async () => {
mockResourceServer.load.mockResolvedValue(false);
const updateResources = new UpdateResources(mockResourceServer, mockResourceRepository);
await updateResources.execute();
expect(mockResourceRepository.saveAll).not.toHaveBeenCalled();
});
});
});
If anyone can offer any improvements, I'm open!
The thing is, you don't actually need to mock an implementation of an interface. The purpose of a mock is to 'look like' the real thing, but if you already have an interface that says what the real thing should look like, any implementation that conforms to the interface will automatically serve equally well as a mock. In fact, from the point of view of the typechecker, there won't be a different between the 'real' and the 'mock' implementation.
Personally what I like to do is to create a mock implementation that can be constructed by feeding it mock responses. Then it can be reused in any test case by constructing it directly in that test case with the exact responses it should provide. I.e., you 'script' the mock with what it should say by injecting the responses at the time of construction. The difference between it and your mocking implementation is that if it doesn't have a response, it throws a exception and fails the test. Here's an article I wrote that shows this method: https://dev.to/yawaramin/interfaces-for-scaling-and-testing-javascript-1daj
With this technique, a test case might look like this:
it('should save to ResourceRepository if updates are available', async () => {
const updateResources = new UpdateResources(
new MockResourceServer({
checkForUpdates: [true],
getResources: [{}],
}),
new MockResourceRepository({
saveAll: [undefined],
}),
);
const result = await updateResources.execute();
expect(result).toBeTruthy();
});
What I like about these mocks is that all the responses are explicit, and show you the sequence of calls that are happening.

How to Test a Global Event Bus With Vue Test Utils?

I am trying to learn how to test events emitted through a global Event Bus. Here's the code with some comments in the places I don't know what to do.
// EvtBus.js
import Vue from 'vue';
export const EvtBus = new Vue();
<!-- CouponCode.vue -->
<template>
<div>
<input
class="coupon-code"
type="text"
v-model="code"
#input="validate">
<p v-if="valid">
Coupon Redeemed: {{ message }}
</p>
</div>
</template>
<script>
import { EvtBus } from '../EvtBus.js';
export default {
data () {
return {
code: '',
valid: false,
coupons: [
{
code: '50OFF',
discount: 50,
message: '50% Off!'
},
{
code: 'FREE',
discount: 100,
message: 'Entirely Free!'
}
]
};
},
created () {
EvtBus.$on('coupon-applied', () => {
//console.info('had a coupon applied event on component');
});
},
methods: {
validate () {
// Extract the coupon codes into an array and check if that array
// includes the typed in coupon code.
this.valid = this.coupons.map(coupon => coupon.code).includes(this.code);
if (this.valid) {
this.$emit('applied');
// I NEVER see this on the coupon-code.spec.js
EvtBus.$emit('coupon-applied');
}
}
},
computed: {
message () {
return this.coupons.find(coupon => coupon.code === this.code).message;
}
}
}
</script>
// tests/coupon-code.spec.js
import expect from 'expect';
import { mount } from '#vue/test-utils';
import CouponCode from '../src/components/CouponCode.vue';
import { EvtBus } from '../src/EvtBus.js';
describe('Reminders', () => {
let wrp;
beforeEach(() => {
wrp = mount(CouponCode);
});
it('broadcasts the percentage discount when a valid coupon code is applied', () => {
let code = wrp.find('input.coupon-code');
code.element.value = '50OFF';
code.trigger('input');
console.log(wrp.emitted('applied'));
//
// I NEVER see this on the outpout.
// How can I test it through a global event bus rather than
// an event emitted from the component instance?
//
EvtBus.$on('coupon-applied', () => {
console.log('coupon was applied through event bus');
});
// Passes, but not using EvtBus instance.
expect(wrp.emitted('applied')).toBeTruthy;
});
});
So, my doubt is how to test that the global event bus is emitting and listening to events inside components that use that event bus.
So, is it possible to test the global Event Bus using Vue Test Utils or I should use another approach?
If component is using global EventBus, eg that's imported outside of given component and assigned to window.EventBus, then it's possible to use global Vue instance to redirect $on or $emit events to wrapper's vm instance. That way you can proceed writing tests as if component is emitting via this.$emit instead of EventBus.$emit:
it('clicking "Settings" button emits "openSettings"', () => {
global.EventBus = new Vue();
global.EventBus.$on('openSettings', (data) => {
wrapper.vm.$emit('openSettings', data);
});
// component emits `EventBus.$emit('openSettings')`
expect(wrapper.emitted('openSettings')).toBeTruthy(); // pass
});
Well,
EvtBus.$on('coupon-applied', () => {
console.log('coupon was applied through event bus');
});
This code in your spec file won't be called because the mounted wrp component is not using the same EvtBus you are importing in your spec file above.
What you require to test this is an npm package named inject-loader so that you can provide your own implementation(stub) of the EvtBus dependency of your coupon code component.
Somewhat like this
const couponCodeInjector = require('!!vue-loader?inject!src/views/CouponCode');
const stubbedModules = {
'../EvtBus.js': {
$on : sandbox.spy((evtName, cb) => cb());
}
};
const couponCode = couponCodeInjector(stubbedModules);
and then in your unit test you can assert whether the stubbedModules['../EvtBus.js'].$on has been called or not when code.trigger('input');
PS: I haven't used vue-test-utils. So I don't know exactly how to the stubbing with this npm package.
But the main thing you need to do is to find a way to stub your EvtBus dependency in the CouponCode component in such a way that you can apply a spy on it and check whether that spy has been called or not.
Unit tests should focus on testing a single component in isolation. In this case, you want to test if the event is emitted, since that is the job of CouponCode.vue. Remember, unit tests should focus on testing the smallest units of code, and only test one thing at a time. In this case, we care that the event is emitted -- EventBus.test.js is where we test what happens when the event is emitted.
Noe that toBeTruthy is a function - you need (). expect(wrp.emitted('applied')).toBeTruthy is actually not passing, since you need () - at the moment, it is actually doing nothing -- no assertion is made.
What your assertion should look like is:
expect(wrp.emitted('applied')).toBeTruthy()
You can go one step further, and ensure it was only emitted once by doing something like expect(wrp.emitted().applied.length).toBe(1).
You then test InputBus in isolation, too. If you can post the code for that component, we can work through how to test it.
I worked on a big Vue app recently and contributed a lot to the main repo and documentation, so I'm happy to help out wherever I can.
Let me know if that helps or you need more guidance. If possible, post EventBus.vue as well.
I got the same issue with vue-test-utils and Jest. For me, createLocalVue() of vue-test-utils library fixed the issue. This function creates a local copy of Vue to use when mounting the component. Installing plugins on this copy of Vue prevents polluting the original Vue copy. (https://vue-test-utils.vuejs.org/api/options.html#localvue)
Adding this to your test file will fix the issue:
const EventBus = new Vue();
const GlobalPlugins = {
install(v) {
// Event bus
v.prototype.$bus = EventBus;
},
};
// create a local instance of the global bus
const localVue = createLocalVue();
localVue.use(GlobalPlugins);
jest.mock('#/main', () => ({
$emit: jest.fn(),
}));
Include this in code in your spec file at the very begining.
Note: '#/main' is the file from which you are importing Event Bus.

Vue unit testing - mocking imported services when using vue-test-utils mount

I'm playing with mount() from vue-test-utils, have a component that imports services that should be mocked in the unit test.
I see that mount() has a mocks option, but trying to extrapolate the example given at guides, common-tips, mocking injections to the scenario of an injected service is eluding me.
mount(Component, {
mocks: {
...?
}
})
The component simply imports the service, which is plain JS
import DataService from '../services/data.service'
I can get it working using the inject-loader which is detailed here Testing With Mocks
The code that does work
const MyComponentInjector = require('!!vue-loader?inject!./MyComponent.vue')
const mockedServices = {
'../services/data.service': {
checkAll: () => { return Promise.resolve() }
},
}
const MyComponentWithMocks = MyComponentInjector(mockedServices)
const wrapper = mount(MyComponentWithMocks, { store: mockStore, router })
What is the syntax for mount(MyComponent, { mocks: ... })?
Since mount() has a mocks option, should it not be possible to pass mockedServices to it in some form?
mocks refers to the Vue instance. You're trying to mock a file dependency, which is a different problem. As you said, one solution is inject-loader. Another is the babel-plugin-rewire.
Let me clear up what the mocks option does.
mocks adds properties to the Vue instance.
If you have an app that injects $route, you might have a component that tries to access it: this.$route.path:
...
methods: {
logPath() {
console.log(this.$route.path)
}
}
...
If you try to mount this component without installing Vue router, it will throw an error. To solve this, you can use the mocks mount option to inject a mock $route object to the Vue instance:
const $route = { path: 'some/mock/value' }
mount(Component, {
mocks: {
$route
}
})

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