Yone
Yone

Reputation: 2134

Angular How to test a Component which requires a Location

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: enter image description here

And the browser never ends loading the suite: enter image description here

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:

enter image description here

Additionaly I have also read:

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

Upvotes: 13

Views: 17232

Answers (3)

Maclean Pinto
Maclean Pinto

Reputation: 1135

     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

Upvotes: 9

Armen Vardanyan
Armen Vardanyan

Reputation: 3315

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

Upvotes: 11

user4676340
user4676340

Reputation:

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.

Upvotes: 3

Related Questions