Jasmine error while testing redux dispatch - javascript

I'm throwing a coin in the ocean after spending a couple hours trying to fix an ngRedux testing error.
Here is the component itself, as you can see, it's calling the ModuleActions:
#Component
class TestedClass {
constructor(private actions: ModuleActions) {}
restoreDefault() {
this.action.reloadPageModule(true);
}
}
Here is the module action class, I'm using an if to dispatch the action or not, because I also call it from an epic:
#Injectable()
export class ModuleActions {
constructor(private ngRedux: NgRedux<PageModules>) {}
...
reloadPageModule(dispatch?: boolean) {
if(dispatch) {
this.ngRedux.dispatch({
type: ModuleActions.RELOAD_PAGE_MODULES,
meta: { status: 'success' }
});
} else {
return {
type: ModuleActions.RELOAD_PAGE_MODULES,
meta: { status: 'success' }
};
}
}
}
And this is my testedClass component test:
it('should fire the reload page module action', () => {
const executeActionSpy = spyOn(component['action'], 'loadSucceeded');
component.restoreDefault();
expect(executeActionSpy).toHaveBeenCalled()
})
...
const compileAndCreate = () => {
TestBed.configureTestingModule({
declarations: [
...
],
providers: [
ModuleActions,
NgRedux
]
}).compileComponents().then(() => {
fixture = TestBed.createComponent(EditorComponent);
component = fixture.componentInstance;
})
...
This is giving me a this.ngRedux.dispatch() is not a function error when called from the

In order to fix it, I modified my test to use an import of NgReduxTestingModule instead of a providers of NgRedux:
import { NgReduxTestingModule } from '#angular-redux/store/testing';
TestBed.configureTestingModule({
imports: [NgReduxTestingModule, ...],
providers: [
...

Related

How to unit test Angular's Meta service?

My Component is using Angular's Meta service to update a meta tag during ngOnInit. I'm using my RegionService to get an app-id and set it with Meta's updateTag method via a template literal. But my unit test is having problems getting the value set by my RegionService in the template literal. The test returns the following error:
Expected spy Meta.updateTag to have been called with:
[ Object({ name: 'apple-itunes-app', content: 'app-id=0123456789' }) ]
but actual calls were:
[ Object({ name: 'apple-itunes-app', content: 'app-id=undefined' }) ].
How can I modify my test so that it knows the value app-id, set by my template literal ${this.regionService.getAppId()} ?
my.component.ts
import { Component, OnInit } from '#angular/core';
import { RegionService } from 'src/services/region.service';
import { Meta } from '#angular/platform-browser';
export class MyComponent implements OnInit {
constructor(
private regionService: RegionService,
private meta: Meta
) {}
ngOnInit() {
this.meta.updateTag({name: 'apple-itunes-app', content: `app-id=${this.regionService.getAppId()}`});
}
}
my.component.spec.ts
import { ComponentFixture, TestBed, waitForAsync } from '#angular/core/testing';
import { NO_ERRORS_SCHEMA } from '#angular/core';
import { MyComponent } from './my.component';
import { RegionService } from 'src/services/region.service';
import { Meta } from '#angular/platform-browser';
import { RouterTestingModule } from '#angular/router/testing';
describe('MyComponent', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
let regionServiceSpy: jasmine.SpyObj<RegionService>;
let metaServiceSpy: jasmine.SpyObj<Meta>;
beforeEach(
waitForAsync(() => {
const regionServiceSpyObj = jasmine.createSpyObj('RegionService', ['getAppId', 'retrieveABCRegions', 'retrieveDEFRegions']);
const metaServiceSpyObj = jasmine.createSpyObj('Meta', ['updateTag']);
TestBed.configureTestingModule({
declarations: [MyComponent],
imports: [RouterTestingModule],
providers: [
{ provide: RegionService, useValue: regionServiceSpyObj },
{ provide: Meta, useValue: metaServiceSpyObj },
],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();
regionServiceSpy = TestBed.inject(RegionService) as jasmine.SpyObj<RegionService>;
metaServiceSpy = TestBed.inject(Meta) as jasmine.SpyObj<Meta>;
}),
);
beforeEach(() => {
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should set app-id to 0123456789 if selectedRegion is FR', () => {
// arrange
// act
regionServiceSpy.selectedRegion = 'FR';
// assert
expect(metaServiceSpy.updateTag).toHaveBeenCalledWith({name: 'apple-itunes-app', content: 'app-id=0123456789'});
});
});
region.service.ts
import { retrieveABCRegions, retrieveDEFRegions} from 'src/regions';
#Injectable({
providedIn: 'root',
})
export class RegionService {
selectedRegion: Region;
getAppId(): string {
if (retrieveABCRegions().includes(this.selectedRegion)) {
return '111111111';
} else if (retrieveDEFRegions().includes(this.selectedRegion)) {
return '0123456789';
}
}
}
Since you've replaced the RegionService with a SpyObj mock:
{ provide: RegionService, useValue: regionServiceSpyObj }
the real service is no longer being used in your tests (which is the correct approach because you're not testing that service here).
So now you need to define what value the mock service's getAppId() method is going to return. You do that by creating a spy strategy for that method on your spy object.
There are different types of spy strategies you can use, but for your purposes here, probably the simplest is returnValue():
it('should set app-id to 0123456789 if selectedRegion is FR', () => {
// arrange
// act
// regionServiceSpy.selectedRegion = 'FR'; <--- not needed for this test since real service method not being called
regionServiceSpy.getAppId.and.returnValue('0123456789'); // <-- define what value mock service's getAppId returns
// assert
expect(metaServiceSpy.updateTag).toHaveBeenCalledWith({name: 'apple-itunes-app', content: 'app-id=0123456789'});
});
And note that setting regionServiceSpy.selectedRegion = 'FR' is not needed here since the actual service method is not being called.

Angular 10 Test: Component Resolver Data Subscription Error

I've spend the last few days trying to get up to speed with ng test and all the spec files #angular/cli creates when creating components and, well, pretty much else.
As I was working on my own portfolio website, I have come across an issue that I cannot seem to understand or fix.
I have this component (pretty vanilla stuff):
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import { Title } from '#angular/platform-browser'
import { ProjectDetails } from './project-details'
#Component({
selector: 'app-projects-details',
templateUrl: './projects-details.component.html',
styleUrls: ['./projects-details.component.sass']
})
export class ProjectsDetailsComponent implements OnInit {
// Class variables
currentContent: ProjectDetails
constructor(
private route : ActivatedRoute,
private title: Title
) { }
ngOnInit() {
// Assign the data to local variable for use
this.route.data.subscribe(content => {
this.currentContent = content.project.view //<-- This line causes the issue
// Set the title for the Projects view
this.title.setTitle(this.currentContent.view_title)
})
}
}
And this spec file (more vanilla stuff):
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { RouterTestingModule } from '#angular/router/testing'
import { ProjectsDetailsComponent } from './projects-details.component';
import { ProjectDetails } from './project-details'
describe('ProjectsDetailsComponent', () => {
let component: ProjectsDetailsComponent;
let fixture: ComponentFixture<ProjectsDetailsComponent>;
const projectDetails : ProjectDetails = { /* valid object content */ }
beforeEach(async(() => {
TestBed.configureTestingModule({
imports:[
RouterTestingModule
],
declarations: [ ProjectsDetailsComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ProjectsDetailsComponent);
component = fixture.componentInstance;
component.currentContent = projectDetails
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
When running the tests, I get this error:
TypeError: content.project is undefined in http://localhost:9876/_karma_webpack_/main.js (line 1576)
So, I'm not sure exactly what's going on here. No matter what I do, the error prevails.I have a similarly setup component that doesn't have this issue and a side by side comparison shows no differences in the spec.ts file aside from imports.
I tried changing the file to this:
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { RouterTestingModule } from '#angular/router/testing'
import { ProjectsDetailsComponent } from './projects-details.component';
import { ProjectDetails } from './project-details'
import { ActivatedRoute } from '#angular/router';
describe('ProjectsDetailsComponent', () => {
let component: ProjectsDetailsComponent;
let fixture: ComponentFixture<ProjectsDetailsComponent>;
const projectDetails : ProjectDetails = {/* valid content */}
beforeEach(async(() => {
TestBed.configureTestingModule({
// imports:[
// RouterTestingModule
// ],
providers: [
{ provide: ActivatedRoute, useValue: projectDetails }
],
declarations: [ ProjectsDetailsComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ProjectsDetailsComponent);
component = fixture.componentInstance;
component.currentContent = projectDetails
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
Which changes the error to this (which confuses me more):
TypeError: this.route.data is undefined in http://localhost:9876/_karma_webpack_/main.js (line 1575)
The question to the community: how do I fix this? What's the reason this error is coming up?
Instead of providing the raw projectDetails, provide an Observable in its data property:
import {of} from 'rxjs';
...
beforeEach(async(() => {
TestBed.configureTestingModule({
providers: [
// Properly provide the activated route mock object.
{ provide: ActivatedRoute, useValue: { data: of(projectDetails) } }
],
declarations: [ ProjectsDetailsComponent ]
})
.compileComponents();
}));
...
If you look at how you access the route data, you can see that it uses an Observable:
this.route.data.subscribe(content => {...});

How to provide routes with guards for unit testing Angular 6 Component?

I have an Angular 6 component that has input fields. If any of the input fields fail validation if the user tries to navigate away, a guard will trigger on the canDeactivate function. The guard is generic because this logic needs to happen on more than one component in the app. It works beautifully running it, but when I attempt to unit test this behavior, the canDeactivate function in the guard is never reached. The guard itself is reached, but never the function. Am I providing the guard in an incorrect way?
Guard Component Interface
export interface GuardComponent {
canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean;
}
CanDeactivateGuard
export class CanDeactivateGuard implements CanDeactivate<GuardComponent> {
constructor() { }
canDeactivate(component: GuardComponent): Observable<boolean> | Promise<boolean> | boolean {
return component.canDeactivate();
}
}
Component
export class MyComponent implements OnInit, GuardComponent {
...
canDeactivate() {
if (!this.form.invalid) {
return true;
}
this.isError = true;
return false;
}
}
spec
const routes = [
{
path: 'my-component',
component: MyComponent,
canDeactivate: [CanDeactivateGuard],
},
{
path: 'my-component-2',
component: MyComponent2
}
];
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [ RouterTestingModule.withRoutes(routes) ],
declarations: [ MyComponent, MyComponent2 ],
providers: [ CanDeactivateGuard ],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MyComponent);
component = fixutre.componentInstance;
fixture.detectChanges();
});
it('should restrict route change if validation fails', fakeAsync(inject([Router, Location, CanDeactivateGuard], (router: Router, location: Location, guard: CanDeactivateGuard) => {
const textbox = fixture.debugElement.query(By.css('#inputField')).nativeElement;
const canDeactivateSpy = spyOn(guard, 'canDeactivate');
const componentDeactivateSpy = spyOn(component, 'canDeactivate');
// anything lower than 0 will fail the form
textbox.value = '-10';
fixture.detectChanges();
router.navigate(['/my-page-2']).then(() => {
expect(location.path()).toBe('/my-component'); // path doesn't change because guard blocks it due to form being invalid
expect(canDeactivateSpy).toHaveBeenCalled();
expect(componentDeactivateSpy).toHaveBeenCalled();
});
})));
The test fails because the canDeactivate functions are never reached.

Accessing a `declare`d variable from unit tests Angular 5 & Karma/Jasmine

I'm trying to unit test a component which has a variable declared using the declare keyword and it has a couple of methods which are then called later in the component, like so:
import {
Component,
OnInit
} from '#angular/core';
import {
AlertController,
App,
Events,
Platform
} from "ionic-angular";
declare
let PushNotification: any;
#Component({
selector: 'push',
template: ``
})
export class PushComponent implements OnInit {
nav: any;
_push: any;
constructor() {}
ngOnInit() {
// ...
this._push = PushNotification.init({
android: {
senderID: '...'
},
ios: {
alert: "true",
badge: false,
sound: 'false'
},
windows: {}
});
/**
* On Registration, we want to register the device with BE
*/
this._push.on('registration', (data) => {
// ...
});
/**
* If we have a notification come through we need to work out what we are doing with it
*/
this._push.on('notification', (notification) => {
// ...
});
}
/**
* Based on the payload we need to perform different actions
* #param payload
*/
public pushAction(payload: any) {
// ...
}
/**
* The user has received a notification while using the app so we need to show an alert
* #param notification
*/
public pushAlert(notification: any) {
// ...
}
/**
* Reset the badge number to 0
*/
public resetBadge() {
// ...
}
/**
* Register deviceId and platform
* #param registrationId
*/
public registerPush(registrationId) {
// ...
}
/**
* De-Register the deviceId
* #param deviceId
*/
public deRegisterPush(deviceId) {
// ...
}
}
In my unit tests, I want to be able to assert that the init() and on() methods have been called, but I always get an error saying that PushNotification is undefined, so I was hoping somebody would be able to tell me how I can access it or inject it into the component somehow?
My test file is below:
// Core files and modules
import {
TestBed,
ComponentFixture
} from '#angular/core/testing';
import {
NO_ERRORS_SCHEMA
} from '#angular/core';
import {
App,
AlertController,
Events,
Platform,
Config
} from 'ionic-angular';
import {
ViewController,
Slides
} from 'ionic-angular';
import {
mockOpportunityProvider,
mockPushProvider,
mockAuth,
mockAppPlatform,
mockPlatform,
mockEvents,
presentableControllerMock
} from '../../mocks';
import {
OpportunityProvider
} from '../../providers/opportunity/opportunity.provider';
import {
AuthProvider
} from '../../providers/auth/auth';
import {
ProfileComponent
} from '../../pages/profile/profile.component';
import {
PushProvider
} from '../../providers/push/push.provider';
// Pages
import {
PushComponent
} from './push.component';
import {
OpportunityComponent
} from '../../pages/opportunity/opportunity.component';
let component: PushComponent;
let fixture: ComponentFixture < PushComponent > ;
describe('Component: ProfileTemplateComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [],
declarations: [PushComponent],
providers: [{
provide: OpportunityProvider,
useClass: mockOpportunityProvider
},
{
provide: AuthProvider,
useClass: mockAuth
},
{
provide: PushProvider,
useClass: mockPushProvider
},
{
provide: App,
useClass: mockAppPlatform
},
ProfileComponent,
{
provide: Platform,
useClass: mockPlatform
},
Config,
{
provide: AlertController,
useClass: presentableControllerMock
},
{
provide: Events,
useClass: mockEvents
}
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(PushComponent);
component = fixture.componentInstance;
});
afterEach(() => {
fixture.destroy();
component = null;
});
// TODO - figure out how to access the PushNotification variable
it('expect ngOnInit() to call ', async() => {
// ...
});
it('expect pushAction() to call nav.setRoot()', () => {
// ...
});
it('expect pushAlert() to call alertCtrl.create() and present()', () => {
// ...
});
it('expect resetBadge() to call _push.setApplicationIconBadgeNumber() if on cordova platform', () => {
// ...
});
it('expect registerPush() to call pushProvider.register()', () => {
// ...
});
it('expect deRegisterPush() to call pushProvider.deRegister()', () => {
// ...
});
});
Thanks in advance!

Can't run service in Angular2 test (code copy pasted from the docs)

I have the following error (unit testing Angular2):
Cannot configure the test module when the test module has already been
instantiated. Make sure you are not using inject before
TestBed.configureTestingModule
Here is my code (it's basically a copy paste from the angular docs) which throws the above error:
import { TestBed, async } from '#angular/core/testing';
import { By } from '#angular/platform-browser';
import { AppModule } from './app.module';
import { AppComponent } from './app.component'
import { MyServiceService } from './my-service.service'
beforeEach(() => {
// stub UserService for test purposes
let userServiceStub = {
isLoggedIn: true,
user: { name: 'Test User'}
};
TestBed.configureTestingModule({
declarations: [ AppComponent],
providers: [ {provide: MyServiceService, useValue: userServiceStub } ]
});
let fixture = TestBed.createComponent(AppComponent);
let comp = fixture.componentInstance;
// UserService from the root injector
let userService = TestBed.get(MyServiceService);
// get the "welcome" element by CSS selector (e.g., by class name)
let de = fixture.debugElement.query(By.css('.nesto'));
let el = de.nativeElement;
it('should welcome "Bubba"', () => {
userService.user.name = 'something'; // welcome message hasn't been shown yet
fixture.detectChanges();
expect(el.textContent).toContain('some');
});
});
I want to run a service but it seems that I just can't do that.
The most likely problem is that you're attempting to run testing within your beforeEach(). You need to make sure all it() methods are outside/after the beforeEach():
beforeEach(() => {
// stub UserService for test purposes
let userServiceStub = {
isLoggedIn: true,
user: { name: 'Test User'}
};
TestBed.configureTestingModule({
declarations: [ AppComponent],
providers: [ {provide: MyServiceService, useValue: userServiceStub } ]
});
let fixture = TestBed.createComponent(AppComponent);
let comp = fixture.componentInstance;
// get the "welcome" element by CSS selector (e.g., by class name)
let de = fixture.debugElement.query(By.css('.nesto'));
let el = de.nativeElement;
});
it('should welcome "Bubba"', inject([MyServiceService], (userService) => {
userService.user.name = 'something'; // welcome message hasn't been shown yet
fixture.detectChanges();
expect(el.textContent).toContain('some');
}));
This works, removed the instance in the beforeEach() and injected into the it().
All credits go to Z. Bagley
import { TestBed, async, inject } from '#angular/core/testing';
import { By } from '#angular/platform-browser';
import { AppModule } from './app.module';
import { AppComponent } from './app.component'
import { MyServiceService } from './my-service.service'
import { Inject } from '#angular/core';
beforeEach(() => {
// stub UserService for test purposes
let userServiceStub = {
isLoggedIn: true,
user: { name: 'Test User'}
};
TestBed.configureTestingModule({
declarations: [ AppComponent],
providers: [ {provide: MyServiceService, useValue: userServiceStub } ]
});
});
it('should welcome "Bubba"', inject([MyServiceService], (userService) => {
let fixture=TestBed.createComponent(AppComponent);
fixture.detectChanges();
expect(userService.user.name).toContain('se');
}));

Categories

Resources