4

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) {
  }
});
halfer
  • 18,701
  • 13
  • 79
  • 158
Mj1992
  • 3,124
  • 10
  • 54
  • 97

1 Answers1

5

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 }]
Alex K
  • 1,747
  • 9
  • 12
  • This was awesome, good shout. For anyone else, use can use the above solution for the `data` property as well. – godhar Oct 15 '20 at 07:45