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 => {...});
Related
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.
I got below error while writing the unit test in Angular using Jest. I have gone through all the similar suggestions here in stackoverflow, but none were helpful.
Error: unsafe value used in a resource URL context (see https://g.co/ng/security#xss) Jest
Below is the code from test file for your review.
import { CUSTOM_ELEMENTS_SCHEMA } from '#angular/core';
import { ComponentFixture, TestBed } from '#angular/core/testing';
import { RouterTestingModule } from '#angular/router/testing';
import { HttpClientTestingModule } from '#angular/common/http/testing';
import { DomSanitizer, SafeResourceUrl } from '#angular/platform-browser';
import { MaterialModule } from '../../Material/material.module';
import { DashboardViewComponent } from './dashboard-view.component';
describe('DashboardViewComponent', () => {
let component: DashboardViewComponent;
let fixture: ComponentFixture<DashboardViewComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
RouterTestingModule,
HttpClientTestingModule,
MaterialModule,
],
declarations: [ DashboardViewComponent ],
providers: [{
provide: DomSanitizer,
useValue: {
bypassSecurityTrustResourceUrl: (): SafeResourceUrl => ''
}
}],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(DashboardViewComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
test('should create', () => {
// expect(component).toMatchSnapshot();
expect(component).toBeTruthy();
});
});
Thanks in advance.
Hi have some error with testing my App made with Angular 7. I do not have much experience in angular, so I would need your help+
Error: StaticInjectorError(DynamicTestModule)[BeerDetailsComponent -> ActivatedRoute]:
StaticInjectorError(Platform: core)[BeerDetailsComponent -> ActivatedRoute]:
NullInjectorError: No provider for ActivatedRoute!
the testing code is like this:
import { async, ComponentFixture, TestBed, inject } from '#angular/core/testing';
import { BeerDetailsComponent } from './beer-details.component';
import {
HttpClientTestingModule,
HttpTestingController
} from '#angular/common/http/testing';
describe('BeerDetailsComponent', () => {
let component: BeerDetailsComponent;
let fixture: ComponentFixture<BeerDetailsComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
declarations: [ BeerDetailsComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(BeerDetailsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create',
inject(
[HttpTestingController],
() => {
expect(component).toBeTruthy();
}
)
)
});
I really can't find any solution.
Daniele
You have to import RouterTestingModule
import { RouterTestingModule } from "#angular/router/testing";
imports: [
...
RouterTestingModule
...
]
Add the following import
imports: [
RouterModule.forRoot([]),
...
],
I had this error for some time as well while testing, and none of these answers really helped me. What fixed it for me was importing both the RouterTestingModule and the HttpClientTestingModule.
So essentially it would look like this in your whatever.component.spec.ts file.
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ProductComponent],
imports: [RouterTestingModule, HttpClientTestingModule],
}).compileComponents();
}));
You can get the imports from
import { RouterTestingModule } from "#angular/router/testing";
import { HttpClientTestingModule } from "#angular/common/http/testing";
I dont know if this is the best solution, but this worked for me.
This issue can be fixed as follows:
In your corresponding spec.ts file import RouterTestingModule
import { RouterTestingModule } from '#angular/router/testing';
In the same file add RouterTestingModule as one of the imports
beforeEach(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule],
providers: [Service]
});
});
Example of a simple test using the service and ActivatedRoute! Good luck!
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { RouterTestingModule } from '#angular/router/testing';
import { HttpClientModule } from '#angular/common/http';
import { MyComponent } from './my.component';
import { ActivatedRoute } from '#angular/router';
import { MyService } from '../../core/services/my.service';
describe('MyComponent class test', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
let teste: MyComponent;
let route: ActivatedRoute;
let myService: MyService;
beforeEach(async(() => {
teste = new MyComponent(route, myService);
TestBed.configureTestingModule({
declarations: [ MyComponent ],
imports: [
RouterTestingModule,
HttpClientModule
],
providers: [MyService]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('Checks if the class was created', () => {
expect(component).toBeTruthy();
});
});
This is my test file and I am trying to identify the error preventing me from running a successful test:
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { Component, Directive, Input, OnInit } from '#angular/core';
import { TestComponent } from './test.component';
import { NgbDropdownModule, NgbCollapse } from '#ng-bootstrap/ng-bootstrap';
import { CommonModule } from '#angular/common';
import { Routes } from '#angular/router';
import { RouterTestingModule } from '#angular/router/testing';
import { NO_ERRORS_SCHEMA } from '#angular/core';
import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from '#angular/platform-browser-dynamic/testing';
let comp: TestComponent;
let fixture: ComponentFixture<MyComponent>;
describe('TestComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ TestComponent ],
providers: [
// DECLARE PROVIDERS HERE
{ provide: TestingCompilerFactory }
]
}).compileComponents()
.then(() => {
fixture = TestBed.createComponent(TestComponent);
comp = fixture.componentInstance;
});
}));
it('should be created', () => {
expect(TestComponent).toBeTruthy();
});
I am getting this error which I guess is because I am not wrapping it correctly.
error TS1005: ';' expected.
But I also get
No provider for TestingCompilerFactory
First fix your syntax error1,2
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [TestComponent],
providers: [{ provide: TestingCompilerFactory }]
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(TestComponent);
comp = fixture.componentInstance;
});
}); // excess `)` removed
Now, onto the noteworthy error
A provider takes can take two forms.
The first is a value that acts as both the value provided and the key under which it is registered. This commonly takes the form of a class as in the following example
const Dependency = class {};
#NgModule({
providers: [Dependency]
}) export default class {}
The second is an object with a provide property specifying the key under which the provider is registered and one or more additional properties specifying the value being provided. A simple example is
const dependencyKey = 'some key';
const Dependency = class {};
#NgModule({
providers: [
{
provide: dependencyKey,
useClass: Dependency
}
]
}) export default class {}
From the above it you can infer that you failed to specify the actual value provided under the key TestingCompilerFactory.
To resolve this write
TestBed.configureTestingModule({
declarations: [TestComponent],
providers: [
{
provide: TestingCompilerFactory,
useClass: TestingCompilerFactory
}
]
})
which is redundant and can be replaced with
TestBed.configureTestingModule({
declarations: [TestComponent],
providers: [TestingCompilerFactory]
})
as described above.
Notes
In the future do not post questions that include such an obvious error - fix it yourself instead.
Do not post two questions as one.
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');
}));