Angular How to test a Component which requires a Location - javascript

Hello and thank you for your time!
I am learning how to use Angular and I am interested in learning how to test its Components.
Currently I am struggling because I have done the Tour of Heroes tutorial of the Angular page and I am testing the code to understand it better.
The point is that I am testing hero-details component which code is:
import {Component, OnInit, Input} from '#angular/core';
import {ActivatedRoute} from '#angular/router';
import {MyHeroService} from '../hero-service/my-hero.service';
import {Location} from '#angular/common';
import {Hero} from '../Hero';
#Component({
selector: 'app-hero-details',
templateUrl: './hero-details.component.html',
styleUrls: ['./hero-details.component.css']
})
export class HeroDetailsComponent implements OnInit {
#Input() hero: Hero;
constructor(private route: ActivatedRoute,
private myHeroService: MyHeroService,
private location: Location) {
}
ngOnInit(): void {
this.getHero();
}
getHero(): void {
const id = +this.route.snapshot.paramMap.get('id');
this.myHeroService.getHero(id)
.subscribe(hero => this.hero = hero);
}
goBack(): void {
this.location.back();
}
}
And my test tries to prove that getHero() is called after creating the hero-details component:
import {HeroDetailsComponent} from './hero-details.component';
import {ActivatedRoute} from '#angular/router';
import {MyHeroService} from '../hero-service/my-hero.service';
import {MessageService} from '../message.service';
import {Location} from '#angular/common';
import {provideLocationStrategy} from '#angular/router/src/router_module';
import {BrowserPlatformLocation} from '#angular/platform-browser/src/browser/location/browser_platform_location';
describe('heroDetails', () => {
it('should call getHero after being created', () => {
const heroDetailsComponent = new HeroDetailsComponent(new ActivatedRoute(),
new MyHeroService(new MessageService([])),
new Location(provideLocationStrategy(new BrowserPlatformLocation(['anyParameter']), '/')));
spyOn(heroDetailsComponent, 'getHero');
heroDetailsComponent.ngOnInit();
expect(heroDetailsComponent.getHero()).toHaveBeenCalled();
});
});
The difficulty I am facing is when I try to create a new Location which is a required parameter for the Hero-datail component's constructor.
The first Location's parameter is a PlatformStrategy, so then I used the provider to build it. Also, the provider needs a PlatformLocation() which looks like is abstract so then I chose the only implementation I could find which is BrowserPlatformLocation.
After all that process, the test execution says:
And the browser never ends loading the suite:
The strange thing here is that the IDE indeed finds those modules because I can navigate to them.
Also if I comment out that test, the suite works well:
Additionaly I have also read:
https://angular.io/api/common/Location
https://www.tektutorialshub.com/location-strategies-angular/
https://angular.io/tutorial/toh-pt5
How could I test it in a correct way? Where could I find more information about doing this type of tests properly? How could do this test to mock easily that Location parameter?
Thank you for reading this

Location is a built-in service, you do not need to instantiate it, just mock it:
const locationStub = {
back: jasmine.createSpy('back')
}
Then in your providers array:
providers: [ ..., {provide: Location, useValue: locationStub} ],
Then in your test just call the components goBack method, then use the Injector to get the instance of your service stub, like this:
const location = fixture.debugElement.injector.get(Location);
And then just test, that the back function has been called:
expect(location.back).toHaveBeenCalled();
This should solve the problem. This is, as far as I have seen, the best way to deal with the built-in services, you don't need to test them (Angular team did that already), just mock them and make sure they have been called correctly

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ HeroDetailsComponent ],
providers: [ MyHeroService ],
imports: [ RouterTestingModule ],
providers: [{ provide: Location, useClass: SpyLocation }]
})
.compileComponents();
}));
it('should logout from application', async(() => {
const location: Location = TestBed.get(Location);
expect(location.href).toContain('blablabla url');
}));
Use SpyLocation from #angular/router/testing

If I were you, I would not bother creating tests from scratch : the CLI creates pre-made tests.
In those tests, there's a TestBed, that is used to set up a testing module for testing your component. If you used it, you would only have to import a router testing module.
This would give something like this :
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ HeroDetailsComponent ],
providers: [ MyHeroService ],
imports: [ RouterTestingModule ]
})
.compileComponents();
}));
And just with that, your whole routing strategy is mocked.

Related

How to use window object in angular 7

I want to use scrollTo() function of window object. Directly by accessing window.scrollTo(0,0), i can achieve this functionality. When i googled about using window in angular, people are creating a provider like below one:
import {InjectionToken, FactoryProvider} from '#angular/core';
export const WINDOW = new InjectionToken<Window>('window');
const windowProvider: FactoryProvider = {
provide: WINDOW,
useFactory: () => window,
};
export const WINDOW_PROVIDERS = [windowProvider];
Inside service using like the below code:
import {Injectable, Inject} from '#angular/core';
import {WINDOW} from './window.provider';
#Injectable({
providedIn: 'root',
})
export class WindowService {
constructor(#Inject(WINDOW) public window: Window) {}
}
In module
{provide: WINDOW_PROVIDERS, useValue: window},
Everything works with the above code, but while running test cases i m getting the below error. Almost half of the application test cases are failing with below error
NullInjectorError: No provider for InjectionToken window!
Default Test case for window.service
import { TestBed } from '#angular/core/testing';
import { WindowService } from './window.service';
describe('WindowService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: WindowService = TestBed.get(WindowService);
expect(service).toBeTruthy();
});
});
How to fix this??
You need to set the following configureTestingModule:
TestBed.configureTestingModule({
providers: [WINDOW_PROVIDERS, WindowService]
});

Routing Module loads before APP_INITIALIZER

I have a value that is from of a config file from static AppConfigService.
Described below:
reference code/article: https://blogs.msdn.microsoft.com/premier_developer/2018/03/01/angular-how-to-editable-config-files/
import { Injectable } from '#angular/core';
import { AppConfig } from './app-config';
import { HttpClient } from '#angular/common/http';
import { environment } from 'src/environments/environment';
#Injectable()
export class AppConfigService {
static settings: AppConfig;
constructor(private http: HttpClient) { }
load() {
console.log('is this getting fired before routing module check?');
const jsonFile = `assets/config/config.${environment.name}.json`;
return new Promise<void>((resolve, reject) => {
this.http.get(jsonFile)
.toPromise()
.then((response: AppConfig) => {
AppConfigService.settings = <AppConfig>response;
console.log(AppConfigService.settings);
resolve();
})
.catch((response: any) => {
reject(`Could not load file '${jsonFile}':
${JSON.stringify(response)}`);
});
});
}
}
This config gets loaded in my APP_INITIALIZER in the app.module.ts
providers: [
AppConfigService,
{
provide: APP_INITIALIZER,
useFactory: (appConfigService: AppConfigService) => () => {appConfigService.load() },
deps: [AppConfigService], multi: true
}
],
but my routing module, named AppRoutingModule is reading something out of my AppConfigService.settings variable which is crazy enough, UNDEFINED. My application crashes. I expect the APP_INITIALIZER to fire BEFORE AppRoutingModule but this is not the case:
Uncaught TypeError: Cannot read property 'oldUrl' of undefined
oldUrl is a property of AppConfigService.settings. I checked if AppConfigService.settings is set, it IS, properly AFTER routing module is fired but this is not what I want.
I checked some other sources for help. I used the following already as maybe a fix: https://github.com/angular/angular/issues/14615 and https://github.com/angular/angular/issues/14588
#component({})
class App {
constructor(router: Router, loginService: LoginService) {
loginService.initialize();
router.initialNavigation();
}
}
#NgModule({
imports: [
BrowserModule,
RouterModule.forRoot(routes, {initialNavigation: false})
],
declarations: [ App ],
bootstrap: [ App ],
providers: [ Guard, LoginService ]
})
export class AppModule {
}
Unfortunately, the above solution is not fixing my problem. I also tried to put in AppModule but alas, that didn't help either.
Any help is very welcome.
I've solved my App Initialization and Routing with NgRx listening the central state to know when the system is Loaded and activating the route Guards after that.
But for a direct solution, you need to add a Route Guard checking when your service is loaded. So, add a loaded: boolean flag in your Service, and check it from a Guard like this:
https://github.com/angular/angular/issues/14615#issuecomment-352993695
This is better handled with Observables tho, and I'm wiring all with NgRx in my Apps using Facades to facilitate everything:
https://gist.github.com/ThomasBurleson/38d067abad03b56f1c9caf28ff0f4ebd
Best regards.

Angular - Apollo: Client has not been defined yet

I'm using apollo client for graphql. I set up the client in AppApolloModule that I'm importing in AppModule. I'm making a query in a service which is also imported right in the AppModule. Although the service runs before the AppApolloModule runs and hence apollo is not initialized when the query is made and I get this error
Error: Client has not been defined yet
AppApolloModule
imports ....
export class AppApolloModule {
constructor(
apollo: Apollo,
httpLink: HttpLink,
private userService: UserService
) {
console.log("apollo module")
apollo.create({
link: httpLink.create({ uri: `${environment.apiBase}/graphql?${this.myService.token}`}),
cache: new InMemoryCache()
})
}
}
App Module
import { AppApolloModule } from './app.apollo.module';
import { MyService } from './services/my.service';
export class AppModule {
constructor() {
console.log("app module")
}
}
I don't get the two consoles app module and apollo module, since the service runs first, it doesn't find any initialized apollo app and thus breaks the code.
How can I make apollo run before the service or any services for that matter in an efficient and standard way?
This will solve the issue nicely:
import {NgModule} from '#angular/core';
import {HttpClientModule} from '#angular/common/http';
import {ApolloModule, APOLLO_OPTIONS} from 'apollo-angular';
import {HttpLink, HttpLinkModule} from 'apollo-angular-link-http';
import {InMemoryCache} from 'apollo-cache-inmemory';
export function createApollo(httpLink: HttpLink) {
return {
link: httpLink.create({uri: 'https://api.example.com/graphql'}),
cache: new InMemoryCache(),
};
}
#NgModule({
imports: [HttpClientModule, ApolloModule, HttpLinkModule],
providers: [
{
provide: APOLLO_OPTIONS,
useFactory: createApollo,
deps: [HttpLink],
},
],
})
class AppModule {}
The answer by #wendellmva didn't work for me. What did work was the solution suggested in this repo:
https://github.com/patricknazar/angular-lazy-loading-apollo-client
which is basically to put Apollo initialization in a separate, shared module, and include it in your main app module with forRoot().
I have the same issue an the docs from Apollo helped me. Go to 'https://www.apollographql.com/docs/angular/basics/setup/' or copy this:
import { HttpClientModule } from "#angular/common/http";
import { ApolloModule, APOLLO_OPTIONS } from "apollo-angular";
import { HttpLinkModule, HttpLink } from "apollo-angular-link-http";
import { InMemoryCache } from "apollo-cache-inmemory";
#NgModule({
imports: [
BrowserModule,
HttpClientModule,
ApolloModule,
HttpLinkModule
],
providers: [{
provide: APOLLO_OPTIONS,
useFactory: (httpLink: HttpLink) => {
return {
cache: new InMemoryCache(),
link: httpLink.create({
uri: "https://o5x5jzoo7z.sse.codesandbox.io/graphql"
})
}
},
deps: [HttpLink]
}],
})
export class AppModule {}
What worked for me was deleting the .angular folder and serving the application again.

How can I Inject a Service into my test when my service has an injectable in its constructor?

I want to create a test script for my DataService class. I know I need to inject the service into the class but the DataService constructor takes an Apollo injectable. I've found a couple dated solutions that didn't work. Any help will be appreciated!
#Injectable()
export class DataService {
constructor(private apollo: Apollo) {}
...
}
This is the test where I need the DataService:
const chai = require('chai');
const should = chai.should();
const req = require("request-promise");
import {inject} from "#angular/core/testing";
import { DataService } from '../data.service'
describe('User', () => {
beforeEach(() => {
})
it('Can be created.', (done) => {
});
})
You will want to create a TestModule that has a list of providers, which are essentially the rules that Angular2 follows for what to inject when something is requested.
beforeEach(() => {
TestBed.configureTestingModule({
providers: [Apollo] // This will return an instance of the actual Apollo class (you will need to import Apollo in your spec file)
}).compileComponents();
});
This will allow you to inject the Apollo service in the code being tested. However, you may not want to inject the actual Apollo service, in which case you can create a mock apollo class, and tell the test component to inject that fake class in place of Apollo
class MyMockApollo {...} // should mock up any methods that your tests will rely on
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{provide: Apollo, useClass: MyMockApollo} // This will return an instance of MyMockApollo
]
}).compileComponents();
});
A third option is to provide a value rather than a class
providers: [
{provide: Apollo, useValue: mockApolloInstance} // This will return the exact thing you give it
]

Ionic 2: unit test for component with ionic's markup/elements

I have a simple Angular 2 component for an Ionic 2 app. The component uses some Ionic's markup, such as:
<ion-card>
<h3>{{ rawcontent.name }}</h3>
<p *ngIf="rawcontent.description">{{ rawcontent.description }}</p>
</ion-card>
the component .ts is something like:
import { Component, Input } from '#angular/core';
import { NavController } from 'ionic-angular/';
import { Content } from '../../pages/content/content';
#Component({
selector: 'content-detail',
templateUrl: 'content-detail.html'
})
export class ContentDetailComponent {
#Input('data') rawcontent: any = {};
constructor(public nav: NavController) {
}
//other methods
}
I'm trying to write an unit test for it, but I got this error so far:
'ion-card' is not a known element:
1. If 'ion-card' is an Angular component, then verify that it is part of this module.
2. If 'ion-card' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '#NgModule.schemas' of this component
to suppress this message.
I don't know what to do now. In this case, ion-card is an Angular component, I guess. So, what to do next? I think I have to change my beforeEach, addining some config. Can anyone help?
beforeEach(() => TestBed.configureTestingModule({
declarations: [ ContentDetailComponent ],
providers: [
{ provide: NavController, useClass: NavMock }
]})
);
You need to import ionicModule. Add this in configureTestingModule
imports: [
IonicModule,
],
This is a good starting blog for testing Ionic 2 apps.
You need to configure the app with Ionic module root in beforeEach:
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [MyApp]
providers: [
],
imports: [
IonicModule.forRoot(MyApp)
]
}).compileComponents();
}));

Categories