Search code examples
angularjasmineangular-activatedroute

Jasmine test for ActivatedRoute Angular 7


I am trying to write a simple test for ActivatedRoute. Here's what my test looks like.

it("should check if subscribes are called in init", () => {
    const subRouteSpy = spyOn(activatedRouteStub.paramMap, "subscribe");
    component.ngOnInit();
    expect(subRouteSpy).toHaveBeenCalled();
});

My TestBed config:

const activatedRouteStub = {
  paramMap: {
    subscribe() {
      return of();
    }
  }
};

TestBed.configureTestingModule({
  declarations: [HomeFilterDrawerComponent],
  providers: [
    { provide: ActivatedRoute, useValue: activatedRouteStub }
  ],
  imports: [
    FormsModule,
    StoreModule.forRoot(appReducers),
    HttpClientTestingModule,
    RouterTestingModule
  ]
}).compileComponents();

The test keeps failing giving me Expected spy subscribe to have been called. Not sure what exactly I am doing wrong here.

The code inside ngOnInit of the component.

this.route.paramMap.subscribe(params => {
  if (params["params"].slug !== undefined) {
  }
});

Solution

  • Angular is cloning your activatedRouteStub object when you provide it via useValue. So you're spying on the original stub object, but your component sees a cloned object without the spy attached.

    This is mentioned in the guide

    Always get the service from an injector Do not reference the userServiceStub object that's provided to the testing module in the body of your test. It does not work! The userService instance injected into the component is a completely different object, a clone of the provided userServiceStub.

    To fix this, you need to get a reference to the cloned object using TestBed.get

    let activatedRoute;
    
    const activatedRouteStub = {
      paramMap: {
        subscribe() {
          return of();
        }
      }
    };
    
    TestBed.configureTestingModule({
      declarations: [HomeFilterDrawerComponent],
      providers: [
        { provide: ActivatedRoute, useValue: activatedRouteStub }
      ],
      imports: [
        FormsModule,
        StoreModule.forRoot(appReducers),
        HttpClientTestingModule,
        RouterTestingModule
      ]
    }).compileComponents();
    
    beforeEach(() => {
      fixture = TestBed.createComponent(NewsComponent);
      component = fixture.componentInstance;
    
      // Get a reference to the injected value
      activatedRoute = TestBed.get(ActivatedRoute);
    });
    
    it("should check if subscribes are called in init", () => {
      // Spy on the injected value
      const subRouteSpy = spyOn(activatedRoute.paramMap, "subscribe");
      component.ngOnInit();
      expect(subRouteSpy).toHaveBeenCalled();
    });
    

    Alternatively, you can keep your code the same, but change useValue to useFactory. This will allow you to bypass the cloning behavior:

    providers: [{ provide: ActivatedRoute, useFactory: () => activatedRouteStub }]