Angular2 Component Unit Testing with Service Dependency - unit-testing

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....

Related

jasmine serivce function not returning mock value

I am work on the angular testing for first time and from monring i struggling to test this. I added both modules inside TestBed and injected but real method is always called instead of returning value from getMockFeatureState(). Please help fix this. Thannks
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ HttpClientModule ],
providers: [ FeatureToggleService, ConfigService]
});
});
it('should return values based on mock feature state', () => {
const featureService = TestBed.inject(FeatureToggleService);
const configService = TestBed.inject(ConfigService);
const mockFeaturesSpy = spyOn(configService, 'getMockFeatures')
.and.returnValue(getMockFeatureState());
expect(featureService.isFeatureEnabled('feature1')).toBeFalsy();
expect(featureService.isFeatureEnabled('feature2')).toBeFalsy();
});
I recently faced the same issue in one of my tests. The order of TestBed.inject was the issue. If FeatureToggleService requires ConfigService. You have to inject ConfigService first and mock it before injecting the featureService. You can try
it('should return values based on mock feature state', () => {
const configService = TestBed.inject(ConfigService);
const mockFeaturesSpy = spyOn(configService, 'getMockFeatures')
.and.returnValue(getMockFeatureState());
const featureService = TestBed.inject(FeatureToggleService);
expect(featureService.isFeatureEnabled('feature1')).toBeFalsy();
expect(featureService.isFeatureEnabled('feature2')).toBeFalsy();
});
I also moved the injection of dependency service into beforeEach block like mentioned in other answer so that it can be applied for other tests too

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.

Mocking BsModalRef for Unit Testing

I am using the BsModalRef for showing modals and sending data using the content property. So we have some like this :
this.followerService.getFollowers(this.bsModalRef.content.channelId).subscribe((followers) => {
this.followerList = followers;
this.followerList.forEach((follower) => {
follower.avatarLink = this.setUserImage(follower.userId);
this.followerEmails.push(follower.email);
});
});
We are setting the channelId in content of bsModalRef (this.bsModalRef.content.channelId). It is working fine. Now i am writing a unit test for this. Problem is i am not able to mock it. I have tried overriding, spy etc but nothing seems to work. I am using the approach mentioned in this link. One alternative is to use TestBed but i am not much aware of its use. Can anyone please help me finding any approach by which this can be achieved ?
I recently had to do something similar and Mocking the method call worked. The tricky part is injecting the BsModalService in both the test suite and the component.
describe('MyFollowerService', () => {
configureTestSuite(() => {
TestBed.configureTestingModule({
imports: [...],
declarations: [...],
providers: [...]
}).compileComponents();
});
// inject the service
beforeEach(() => {
bsModalService = getTestBed().get(BsModalService);
}
it('test', () => {
// Mock the method call
bsModalService.show = (): BsModalRef => {
return {hide: null, content: {channelId: 123}, setClass: null};
};
// Do the test that calls the modal
});
});
As long as you're calling bsModal as follows this approach will work
let bsModalRef = this.modalService.show(MyChannelModalComponent));
Finally, here are some links that have more indepth coverage about setting up the tests with TestBed.
https://chariotsolutions.com/blog/post/testing-angular-2-0-x-services-http-jasmine-karma/
http://angulartestingquickstart.com/
https://angular.io/guide/testing

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: How to mock ChangeDetectorRef while unit testing

I have just started with Unit-Testing, and I have been able to mock my own services and some of Angular and Ionic as well, but no matter what I do ChangeDetectorRef stays the same.
I mean which kind of sorcery is this?
beforeEach(async(() =>
TestBed.configureTestingModule({
declarations: [MyComponent],
providers: [
Form, DomController, ToastController, AlertController,
PopoverController,
{provide: Platform, useClass: PlatformMock},
{
provide: NavParams,
useValue: new NavParams({data: new PageData().Data})
},
{provide: ChangeDetectorRef, useClass: ChangeDetectorRefMock}
],
imports: [
FormsModule,
ReactiveFormsModule,
IonicModule
],
})
.overrideComponent(MyComponent, {
set: {
providers: [
{provide: ChangeDetectorRef, useClass: ChangeDetectorRefMock},
],
viewProviders: [
{provide: ChangeDetectorRef, useClass: ChangeDetectorRefMock},
]
}
})
.compileComponents()
.then(() => {
let fixture = TestBed.createComponent(MyComponent);
let cmp = fixture.debugElement.componentInstance;
let cdRef = fixture.debugElement.injector.get(ChangeDetectorRef);
console.log(cdRef); // logs ChangeDetectorRefMock
console.log(cmp.cdRef); // logs ChangeDetectorRef , why ??
})
));
it('fails no matter what', async(() => {
spyOn(cdRef, 'markForCheck');
spyOn(cmp.cdRef, 'markForCheck');
cmp.ngOnInit();
expect(cdRef.markForCheck).toHaveBeenCalled(); // fail, why ??
expect(cmp.cdRef.markForCheck).toHaveBeenCalled(); // success
console.log(cdRef); // logs ChangeDetectorRefMock
console.log(cmp.cdRef); // logs ChangeDetectorRef , why ??
}));
#Component({
...
})
export class MyComponent {
constructor(private cdRef: ChangeDetectorRef){}
ngOnInit() {
// do something
this.cdRef.markForCheck();
}
}
I have tried everything , async, fakeAsync, injector([ChangeDetectorRef], () => {}).
Nothing works.
Update 2020:
I wrote this originally in May 2017, it's a solution that worked great at the time and still works.
We can't configure the injection of a changeDetectorRef mock through the test bed, so this is what I am doing these days:
it('detects changes', () => {
// This is a unique instance here, brand new
const changeDetectorRef = fixture.debugElement.injector.get(ChangeDetectorRef);
// So, I am spying directly on the prototype.
const detectChangesSpy = spyOn(changeDetectorRef.constructor.prototype, 'detectChanges');
component.someMethod(); // Which internally calls the detectChanges.
expect(detectChangesSpy).toHaveBeenCalled();
});
Then you don't care about private attributes or any.
In case anyone runs into this, this is one way that has worked well for me:
As you are injecting the ChangeDetectorRef instance in your constructor:
constructor(private cdRef: ChangeDetectorRef) { }
You have that cdRef as one of the private attributes on the component, which means you can spy on the component, stub that attribute and have it return whatever you want. Also, you can assert its calls and parameters, as needed.
In your spec file, call your TestBed without providing the ChangeDetectorRef as it won't provide what you give it. Set the component that same beforeEach block, so it is reset between specs as it is done in the docs here:
component = fixture.componentInstance;
Then in the tests, spy directly on the attribute
describe('someMethod()', () => {
it('calls detect changes', () => {
const spy = spyOn((component as any).cdRef, 'detectChanges');
component.someMethod();
expect(spy).toHaveBeenCalled();
});
});
With the spy you can use .and.returnValue() and have it return whatever you need.
Notice that (component as any) is used as cdRef is a private attribute. But private doesn't exist in the actual compiled javascript so it is accessible.
It is up to you if you want to access private attributes at runtime that way for your tests.
Not sure if this a new thing or not, but changeDetectorRef can be accessed via fixture.
See docs: https://angular.io/guide/testing#componentfixture-properties
We ran into the same issue with change detector mocking and this is ended up being the solution
Probably one point that needs to be pointed out, is that in essence here you want to test your own code, not unit test the change detector itself (which was tested by the Angular team).
In my opinion this is a good indicator that you should extract the call to the change detector to a local private method (private as it is something you don't want to unit test), e.g.
private detectChanges(): void {
this.cdRef.detectChanges();
}
Then, in your unit test, you will want to verify that your code actually called this function, and thus called the method from the ChangeDetectorRef. For example:
it('should call the change detector',
() => {
const spyCDR = spyOn((cmp as any).cdRef, 'detectChanges' as any);
cmp.ngOnInit();
expect(spyCDR).toHaveBeenCalled();
}
);
I had the exact same situation, and this was suggested to me as a general best practice for unit testing from a senior dev who told me that unit testing is actually forcing you by this pattern to structure your code better. With the proposed restructuring, you make sure your code is flexible to change, e.g. if Angular changes the way they provide us with change detection, then you will only have to adapt the detectChanges method.
For unit testing, if you are mocking ChangeDetectorRef just to satisfy dependency injection for a component to be creation, you can pass in any value.
For my case, I did this:
TestBed.configureTestingModule({
providers: [
FormBuilder,
MyComponent,
{ provide: ChangeDetectorRef, useValue: {} }
]
}).compileComponents()
injector = getTestBed()
myComponent = injector.get(MyComponent)
It will create myComponent successfully. Just make sure test execution path does not need ChangeDetectorRef. If you do, then replace useValue: {} with a proper mock object.
In my case, I just needed to test some form creation stuff using FormBuilder.
// component
constructor(private changeDetectorRef: ChangeDetectorRef) {}
public someHandler() {
this.changeDetectorRef.detectChanges();
}
// spec
const changeDetectorRef = fixture.componentRef.changeDetectorRef;
jest.spyOn(changeDetectorRef, 'detectChanges');
fixture.detectChanges(); // <--- needed!!
component.someHandler();
expect(changeDetectorRef.detectChanges).toHaveBeenCalled();