'Angular Mock ActivatedRoute using Snapshot and ParamMap

I'm using this, from here:

constructor(private heroService: HeroService,
    private activatedRoute: ActivatedRoute) {
}

ngOnInit() {
    const heroId = this.activatedRoute.snapshot.paramMap.get('id');
    this.heroService.getHeroById(heroId).subscribe((hero: Hero) => {
        ...
    });
}

Now I have a unit test that needs to mock this ActivatedRoute. I've already check some answers here in stackoverflow but without luck.

Notice that I'm using snapshot.paramMap so many solutions do not work at all. For example, this, this, and this do not work.

I think the near valid answer could be:

{
    provide: ActivatedRoute,
    useValue: {paramMap: Observable.of(convertToParamMap({id: 1}))}
}

But does not work either.

The error showing is:

Error: Failed to execute 'send' on 'XMLHttpRequest': Failed to load 'ng:///HeroesModule/HeroDetailComponent_Host.ngfactory.js'.
    error properties: Object({ INDEX_SIZE_ERR: 1, DOMSTRING_SIZE_ERR: 2, HIERARCHY_REQUEST_ERR: 3, WRONG_DOCUMENT_ERR: 4, INVALID_CHARACTER_ERR: 5, NO_DATA_ALLOWED_ERR: 6, NO_MODIFICATION_ALLOWED_ERR: 7, NOT_FOUND_ERR: 8, NOT_SUPPORTED_ERR: 9, INUSE_ATTRIBUTE_ERR: 10, INVALID_STATE_ERR: 11, SYNTAX_ERR: 12, INVALID_MODIFICATION_ERR: 13, NAMESPACE_ERR: 14, INVALID_ACCESS_ERR: 15, VALIDATION_ERR: 16, TYPE_MISMATCH_ERR: 17, SECURITY_ERR: 18, NETWORK_ERR: 19, ABORT_ERR: 20, URL_MISMATCH_ERR: 21, QUOTA_EXCEEDED_ERR: 22, TIMEOUT_ERR: 23, INVALID_NODE_TYPE_ERR: 24, DATA_CLONE_ERR: 25, code: 19 })

This is because I'm doing a request that responses with status 500 because paramMap is undefined so there is no id on the request.

... any ideas?

Thanks and BR



Solution 1:[1]

Add snapshot to the useValue like:

{
          provide: ActivatedRoute,
          useValue: {
            snapshot: {
              paramMap: convertToParamMap({
                id: '1'
              })
            }
          }
        }

Solution 2:[2]

In recent Angular versions, a project's aot setting is on by default (for better compile-time type checking). If this is the case with your project, then you probably need to at least stub out all the properties of ActivatedRoute and ActivatedRouteSnapshot. Something like this:

import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { Type } from '@angular/core';
import { Location } from '@angular/common';
import { MockPlatformLocation } from '@angular/common/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { ActivatedRoute, ActivatedRouteSnapshot, Params, ParamMap, convertToParamMap } from '@angular/router';
import { of, BehaviorSubject } from 'rxjs';

import { HeroDetailComponent } from './hero-detail.component';
import { Hero } from '../hero';


export class MockActivatedRouteSnapshot implements ActivatedRouteSnapshot {
  private innerTestParams?: Params;

  constructor(params?: Params) {
    if (params) {
      this.testParams = params;
    } else {
      this.testParams = null;
    }
  }

  private get testParams() {
    return this.innerTestParams;
  }

  private set testParams(params: Params) {
    this.innerTestParams = params;
  }

  get paramMap() {
    return convertToParamMap(this.testParams);
  }

  get queryParamMap() {
    return this.paramMap;
  }

  get url() {
    return null;
  }

  get fragment() {
    return null;
  }

  get data() {
    return null;
  }

  get outlet() {
    return null;
  }

  get params() {
    return this.innerTestParams;
  }

  get queryParams() {
    return this.innerTestParams;
  }

  get component() {
    return null;
  }

  get routeConfig() {
    return null;
  }

  get root() {
    return null;
  }

  get parent() {
    return null;
  }

  get firstChild() {
    return null;
  }

  get children() {
    return null;
  }

  get pathFromRoot() {
    return null;
  }
}


export class MockActivatedRoute implements ActivatedRoute {
  private innerTestParams?: Params;
  private subject?: BehaviorSubject<Params> = new BehaviorSubject(this.testParams);
  private paramMapSubject?: BehaviorSubject<ParamMap> = new BehaviorSubject(convertToParamMap(this.testParams));

  constructor(params?: Params) {
    if (params) {
      this.testParams = params;
    } else {
      this.testParams = null;
    }
  }

  private get testParams() {
    return this.innerTestParams;
  }

  private set testParams(params: Params) {
    this.innerTestParams = params;
    this.subject.next(params);
    this.paramMapSubject.next(convertToParamMap(params));
  }

  get snapshot() {
    return new MockActivatedRouteSnapshot(this.testParams);
  }

  get params() {
    return this.subject.asObservable();
  }

  get queryParams() {
    return this.params;
  }

  get paramMap() {
    return this.paramMapSubject.asObservable();
  }

  get queryParamMap() {
    return this.paramMap;
  }

  get url() {
    return null;
  }

  get fragment() {
    return null;
  }

  get data() {
    return null;
  }

  get outlet() {
    return null;
  }

  get component() {
    return null;
  }

  get routeConfig() {
    return null;
  }

  get root() {
    return null;
  }

  get parent() {
    return null;
  }

  get firstChild() {
    return null;
  }

  get children() {
    return null;
  }

  get pathFromRoot() {
    return null;
  }
}


describe('HeroDetailComponent', () => {
  let component: HeroDetailComponent;
  let fixture: ComponentFixture<HeroDetailComponent>;
  let httpMock: HttpTestingController;
  let routeMock: MockActivatedRoute;
  let initialMockParams: Params;
  let locationMock: MockPlatformLocation;

  beforeEach(async(() => {
    initialMockParams = {id: 11};
    routeMock = new MockActivatedRoute(initialMockParams);
    locationMock = new MockPlatformLocation;
    TestBed.configureTestingModule({
      imports: [
        HttpClientTestingModule
      ],
      declarations: [ HeroDetailComponent ],
      providers: [
        {
          provide: ActivatedRoute, useValue: routeMock,
        },
        {
          provide: Location, useValue: locationMock,
        }
      ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(HeroDetailComponent);
    component = fixture.componentInstance;
    httpMock = TestBed.inject<HttpTestingController>(HttpTestingController as Type<HttpTestingController>);
    fixture.detectChanges();
  });

  afterEach(() => {
    httpMock.verify();
  });

  it('should be created', () => {
    fixture.detectChanges();

    expect(component).toBeTruthy();

    const dummyHero: Hero = { id: 11, name: 'dummyHero' };
    const req = httpMock.expectOne('api/details/11');
    req.flush(dummyHero);
  });
});

Solution 3:[3]

The accepted answer did not work for me until I changed the syntax a bit. This worked:

This didn't work until I changed a couple things. This worked:

providers: [
 {
  provide: ActivatedRoute,
   useValue: {
    paramMap: of(
     convertToParamMap({
      'dynamic-id': '3',
      'another-dynamic-id': '2',
    })
   ),
  },
 },
],```

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Ibrahim El_Khatib
Solution 2
Solution 3 MikhailRatner