I'm new to Angular, and I have a new team that told me to implement unit testing on an old project with Angular 8. So after investigating that, I decided to use Karma + Jasmine, and I created .spect.ts document like this:
describe("CashFlowSalariesComponent", () => {
let fixture: ComponentFixture < CashFlowSalariesComponent > ;
let instance;
let profile: ProfileModel;
beforeEach(async() => {
TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: [
RouterTestingModule,
FormsModule,
ReactiveFormsModule,
BrowserModule,
HttpClientModule,
ToastrModule.forRoot({
positionClass: "toast-bottom-right",
}),
],
declarations: [CashFlowSalariesComponent],
providers: [
ApiService,
UserService,
ProfileService,
VettingStatusService,
SkillService,
ApplicationRoleService,
SeniorityLevelService,
PlacementStatusService,
EducationLevelService,
UtilsService,
ShirtSizeService,
GenderService,
TimeZoneService,
CountryService,
CityService,
PostalCodeService,
StateService,
ClientSectorService,
JobService,
ClientService,
PulsecheckQuestionService,
PulsecheckMasterService,
ExchangeRateService,
DepartmentService,
ExpenseService,
ProfileRoleService,
SkillCategoriesService,
ProfileActivityService,
ProfileSalaryActivityService,
TimeSheetService,
HolidayService,
RequestTimeOffService,
TimeOffTypeService,
InvoiceService,
PulsecheckDetailService,
],
}).compileComponents();
fixture = TestBed.createComponent(CashFlowSalariesComponent);
instance = fixture.componentInstance;
fixture.detectChanges();
profile = new ProfileModel();
(instance as any).exchangeRate = 18.92;
});
afterEach(() => {
fixture.destroy();
});
it("To test instance creation of component", () => {
expect(instance).toBeTruthy();
});
As you can see, I had to import many services from providers because my ApiService had injected them, so if I no add them, it returned an error like this:
NullInjectorError: No provider for {serviceName}!
So each time I call the ApiService on each component, developers should copy and paste from this component all the services. So my question is: is this a way to create a class or something to import all providers in once without copying and pasting code each time? How can I achieve this?
There are 2 things that could help you with your issue:
You can define your services as providedIn: 'root' in this case you don't need to provide them in any module or test. Angular would resolve them automatically.
example:
#Injectable({ providedIn: 'root' })
export class UserService {}
You could also define global providers in your test.ts file.
example:
#NgModule({
imports: [RouterTestingModule, HttpClientTestingModule],
providers: [...],
})
class GlobalTestingSetupModule {}
TestBed.initTestEnvironment([BrowserDynamicTestingModule, GlobalTestingSetupModule], platformBrowserDynamicTesting());
Also, instead of HttpClientModule it is preferable to use HttpClientTestingModule in unit tests.
Related
I'm trying to export and import a service in NestJS. It seems easy and I thought it should work like this but I got an error saying that Nest can't resolve the dependencies.
SettingsModule
This module has the service that should be imported, and exports it.
#Module({
imports: [
MongooseModule.forFeature([{ name: Setting.name, schema: SettingSchema }]),
],
providers: [SettingsService],
exports: [SettingsService],
})
export class SettingsModule {}
MsgraphModule
This module should import the service through the module because the service is injected in their service.
#Module({
imports: [SettingsModule],
providers: [MsgraphService],
})
export class MsgraphModule {}
AppModule
#Module({
imports: [
MongooseModule.forRoot('mongodb://localhost/lead-import', {
useCreateIndex: true,
}),
MsgraphModule,
SettingsModule,
...
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
What am I doing wrong here?
The problem was that I used the #Inject() decorator which is only needed for custom dependency injections.
#Injectable()
export class MsgraphService {
private client: Client;
private authenticator;
constructor(#Inject() private settingsService: SettingsService) {
this.init();
this.authenticator = new MSGraphAuthenticator();
}
...
}
So removing the #Inject() did the trick.
I have an Angular project that whatever spec file I created to test any component is failing due Error: Illegal state: Could not load the summary for directive ...
For example, I created a component that contains some Material design tags, and belongs to a module called PagesModule, the component does not do anything:
pages.module.ts
import { NgModule } from '#angular/core';
import { RouterModule } from '#angular/router';
import { SharedModule } from '../_shared/shared.module';
import { NotFoundComponent } from './error/not-found/not-found.component';
#NgModule({
declarations: [NotFoundComponent],
imports: [SharedModule, RouterModule],
exports: [],
providers: []
})
export class PagesModule {}
not-found.component.spec.ts
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { MatCardModule } from '#angular/material';
import { NotFoundComponent } from './not-found.component';
describe('NotFoundComponent', () => {
let component: NotFoundComponent;
let fixture: ComponentFixture<NotFoundComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [SharedModule],
declarations: [NotFoundComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(NotFoundComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeDefined();
});
});
Additional information:
My SharedModule is already exporting all necessary material modules.
Your NotFoundComponent may contain nested components or directives, whose templates may contain more components. There are basically two techniques to deal with this in your tests (see. Nested component tests).
create and declare stub versions of the components and directives
add NO_ERRORS_SCHEMA to the TestBed.schemas metadata.
When opting for the first solutions, your test could look something like this.
#Component({selector: 'app-nested', template: ''})
class NestedStubComponent {}
describe('NotFoundComponent', () => {
...
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [SharedModule],
declarations: [NotFoundComponent]
}).compileComponents();
}));
When opting for the second solution, TestBed.configureTestingModule would have to be changed as follows.
TestBed.configureTestingModule({
imports: [SharedModule],
declarations: [NotFoundComponent],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
When running ng test for my Angular app I get this error message for components that have FirebaseApp from angularfire2 injected into the constructor.
I know for a regular service I would add that services to the providers array for that component spec file or if it was the http service I could add the HttpClientModule to the imports for that spec file but what do I do for Firebase?
Have tried doing this with no luck:
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
AngularFireModule.initializeApp(environment.firebase)
],
declarations: [
BlogPostComponent,
HeaderComponent,
FooterComponent
]
}).compileComponents();
fixture = TestBed.createComponent(BlogPostComponent);
component = fixture.componentInstance;
element = fixture.nativeElement;
de = fixture.debugElement;
fixture.detectChanges();
}));
you need to put the FirebaseApp into the providers barrel in your spec
declarations: [
...
],
providers: [FirebaseApp]
and if you need to stub out the values for that app you can do it like this
declarations: [
...
],
providers: [{ provide: FirebaseApp, useValue: someStubHere }]
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.
Below is a link to project on Plunker. where there are two components eager and lazy, also there is shared service which is used on both components.
How to update service variable in a module's component so that the variable in lazy module updates automatically?
[Plunker example][1]
[1]: https://plnkr.co/edit/L2ypUQZiltSPXnLlxBoa?p=preview
You have this behavior because of child injector created new instance of the service. For creating one instance of the service for application, you should use .forRoot() method
import { NgModule, ModuleWithProviders } from '#angular/core';
import { CommonModule } from '#angular/common';
import { SomeComponent } from './other.component';
import { SharedModule } from './shared/index'
import { SomeService } from './some.service';
#NgModule({
imports: [CommonModule, SharedModule],
declarations: [ SomeComponent ],
exports: [ SomeComponent ]
})
export class OtherModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: SharedModule,
// here you put services to share
providers: [SomeService]
}
}
}
// and in AppModule
#NgModule({
imports: [
CommonModule,
SharedModule,
OtherModule.forRoot()
],
declarations: [ SomeComponent ],
exports: [ SomeComponent ]
})
It will allow you to use one instance of SomeService in your components.
I've updated your plunker. Pleas, have a look on changed example
You can achieve this by using an Observable. This observable is stored in your service and other can subscribe to this observable. Below is an example in which I have removed the standard Angular stuff to improve readability:
service.ts
#Injectable()
export class MyService {
// BehaviorSubject sends last known value when someone subscribes.
public isAuthenticated$ = new BehaviorSubject<boolean>(false);
changeAuthenticated() {
// broadcast true to all subscribers
this.isAuthenticated$.next(true);
}
}
component.ts
myService.isAuthenticated$.subscribe(authenticated => {
if (authenticated) {
// do stuff
}
});
So when isAuthenticated$ inside MyService is changed it fires to all subscribers. Please not it doesn't fire if there are no subscribers.
More info about Observables: https://angular-2-training-book.rangle.io/handout/observables/using_observables.html