The Angular test spy is failing to be invoked

Having trouble setting up my Angular test correctly. The issue seems to be with my spy not functioning as expected. I'm new to Angular and still learning how to write tests. This is for my first Angular app using the latest version of CLI 7.x, which is a simple slideshow application. The slideshow itself works fine, but I'm having difficulties getting the tests to pass.

The initial challenge involves extracting the href from the JavaScript window.location. Following advice from Stackoverflow and other sources, I created a class to encapsulate the window object to make it testable. Here's what it looks like in a service named windowobject.service.ts:

import { Injectable } from '@angular/core';

function getWindow (): any {
  return window;
}

@Injectable({
  providedIn: 'root'
})
export class WindowObjectService {

  constructor() { }

  get browserWindow(): any {
    return getWindow();
  }
}

While this setup works perfectly, the problem arises when trying to mock it in the test. My test file is called photos.component.spec.ts:

import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { PhotosComponent } from './photos.component';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap' ;
import { WindowObjectService } from '../services/windowobject.service';
import { environment } from '../../environments/environment';

const expectedId = 916;

class MockWindowObjectService {
  browserWindow(): any {
    return { window: {
                        location: {
                            href: environment.baseUrl + '/angular/slideshow/index.html?id=' + expectedId
                        }
                      }
    };
  }
}

describe('PhotosComponent', () => {
  let component: PhotosComponent;
  let fixture: ComponentFixture<PhotosComponent>;
  let windowService: MockWindowObjectService;
  let windowSpy;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [ HttpClientTestingModule, NgbModule ],
      declarations: [PhotosComponent],
      providers: [ { provide: WindowObjectService, useClass: MockWindowObjectService } ]
    })
    .compileComponents().then(() => {

      windowService = TestBed.get(WindowObjectService);
      fixture = TestBed.createComponent(PhotosComponent);
      component = fixture.componentInstance;
    });
  }));

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should call window object service', () => {
    windowSpy = spyOn(windowService, 'browserWindow').and.callThrough();
    expect(windowService.browserWindow).toHaveBeenCalled();
  });

});

Currently working on mocking the window object service and verifying if it has been called successfully. Once confirmed, the next step would be to check if it returns the mocked value. I've experimented with different configurations of the test, but nothing seems to work. Only receiving an error about the spy not being triggered. Any suggestions on how to resolve this and make the mock & spy functional?

Update: Adding photos.component.ts file. This component utilizes ng-bootstrap carousel & pagination to display a slideshow featuring one image at a time that can be navigated through carousel arrows or pagination. The photo collection ID is extracted from a query string value in the URL.

import { Component, OnInit, ViewChild } from '@angular/core';
import { PhotosService } from '../services/photos.service';
import { IPhoto } from '../models/photo';
import { NgbCarousel, NgbCarouselConfig, NgbPaginationConfig  } from '@ng-bootstrap/ng-bootstrap';
import { WindowObjectService } from '../services/windowobject.service';

@Component({
  selector: 'app-photos',
  templateUrl: './photos.component.html',
  styleUrls: ['./photos.component.scss'],
  providers: [NgbCarouselConfig, WindowObjectService]
})
export class PhotosComponent implements OnInit {

  // reference to "photosCarousel"
  @ViewChild('photosCarousel') photosCarousel: NgbCarousel;

  private _photosService: any;
  private _windowService: WindowObjectService;

  errorMessage: string;
  photos: IPhoto[] = new Array;
  page = 1;
  collectionSize: number;
  tripReportId: string;

  constructor(carouselConfig: NgbCarouselConfig, paginationConfig: NgbPaginationConfig, phototsService: PhotosService,
              windowService: WindowObjectService) {

    carouselConfig.showNavigationArrows = true;
    carouselConfig.showNavigationIndicators = false;
    carouselConfig.interval = 0; // Amount of time in milliseconds before next slide is shown.
    carouselConfig.wrap = false;

    paginationConfig.pageSize = 1;
    paginationConfig.maxSize = 5;
    paginationConfig.size = 'sm';
    paginationConfig.boundaryLinks = false;

    this._photosService = phototsService;
    this._windowService = windowService;
  }

  ngOnInit() {

    console.log('this._windowService.browserWindow.location.href', this._windowService.browserWindow.location.href);

    this.tripReportId = this._windowService.browserWindow.location.href.split('?')[1].split('=')[1];

    this._photosService.getPhotos(this.tripReportId).subscribe(
      photos => {
        this.photos = photos;
        this.collectionSize = photos.length;
      },
      error => this.errorMessage = <any>error
    );

    this.collectionSize = this.photos.length;
  }

  pageChanged(pageNumber: number): void {

    this.photosCarousel.select(pageNumber.toString());
  }

  public onSlide(slideData) {
    this.page = slideData.current;
  }
}

Answer №1

After running your code in this StackBlitz, here is what I discovered:

  • Upon further inspection, it became clear that calling fixture.detectChanges() was necessary to trigger the execution of ngOnInit().
  • Due to the declaration of WindowObjectService in the providers array of the @Component decorator, replacing it with a mock in the default providers array was not feasible. Instead, the solution involved utilizing overrideComponent() as demonstrated in the StackBlitz.
  • The component assigning windowService to a component variable within the constructor posed challenges for mocking with a class, prompting the need to modify the mock to an object.
  • As a result of setting up a variable in the constructor, spyOnProperty() had to be implemented early on, prior to the creation of the component.
  • Directly spying on windowService.browserWindow could lead to complications, hence using the previously established spy variable was advised.
  • Moreover, I provided guidance on how to mock PhotosService with initial data to assist you in getting started with that aspect too.

Successfully, the tests are now passing. I trust this information proves beneficial to you.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Troubleshooting issue: Unable to Subscribe to Subject in Angular 5+ with RxJS

My service has an observable that is subscribed to, and the payload is passed to a subject in the service. However, when I subscribe to the subject in my component, nothing happens. I expect to see the output from console.log in the .subscribe method withi ...

What is the importance of context in the subscription method of RxJS or Angular Observables?

In the given situations, I am providing a child Component with a property that is updated later through an RxJs Observable subscription. Angular fails to detect changes when not using an anonymous function or binding the this context. // Situation 1 // C ...

What is the best way to apply DateRange filtering in a Kendo Ui Grid?

Currently I am working with the Kendo Ui Grid and attempting to implement filtering by DateRange. Here is a snippet of my current code: HTML: <kendo-grid-column field="createdate" title="Creation Date" width="150"> <ng-template kendoGridFilterC ...

Generating an interactive table using JSON with Angular 5

Can a dynamic table with dynamic columns be created based on a JSON object using Angular 5? If yes, how? The API response includes the following JSON: { "ResponseStatus": true, "ResponseData": [ { "Parent": "Company 1", ...

Exploring the possibilities of utilizing package.json exports within a TypeScript project

I have a local Typescript package that I am importing into a project using npm I ./path/to/midule. The JSON structure of the package.json for this package is as follows: { "name": "my_package", "version": "1.0.0&q ...

Creating an Ionic v4 alert box that redirects users to different pages

I am facing an issue with my ion-alert component where I have set up a message with two options, "To Myself" and "To Someone", that should act like buttons and route to different pages in the application. However, using (click) events or [routerLink] on th ...

Information obtained from the visible is consistently indefinable

I provide a service that returns observables of an array of objects allItems: Item[] = [ { id: "1", name: "item 1" }, { id: "2", name: "item 2" }, { id: "3" ...

Ways to retrieve the component name from a service in Angular without relying on private APIs such as view container refs

How can I access the component name inside a service that is calling a method within the service? I have tried using console.log(this.vcr['_view'].component) and console.log(this.vcr['_view'].component.constructor.name), but they do not ...

Extracting and transforming an array into a list with the desired outcome

Looking for a way to flatten the array below into a single line array using Typescript/JavaScript? Student: any = [ { "id": "1", "name": "Jhon", "Marks": { "Math": "90", "English": "80", "Science": "70" } }, { "id": "2", "name": "Peter", "Marks": { "M ...

What is the best way to simulate a library in jest?

Currently, I am attempting to simulate the file-type library within a jest test scenario. Within my javascript document, this particular library is utilized in the subsequent manner: import * as fileType from 'file-type'; .... const uploadedFil ...

Divide a given number of elements within an array of arrays

Presented below is an array: [ { "id": "34285952", "labs": [ { "id": "13399-17", "location": "Gambia", "edge": ["5062-4058-8562-294 ...

What is the best way to insert CSS code into a custom Vue directive file?

I need a solution that applies a gradient background to the parent div in case an image fails to load. I've attempted to create a directive for this purpose: export default { bind(el: any, binding: any) { try { ..... ...

Is it possible to apply CSS to the alert and confirm functions in Angular?

Currently, I am facing an issue with implementing CSS on elements that are nested inside a function. I am unsure of how to access them properly. For example: updateUser() { this.usersService.UpdateUser(this.user.id, this.user) .subscribe(() =& ...

I'm having trouble viewing anything on my localhost with Angular app using Docker

I recently attempted to dockerize an Angular application and encountered some issues. I experimented with two different Dockerfiles in an attempt to resolve the problem but was unsuccessful. The first Dockerfile I tried is as follows: FROM node:latest as n ...

Tips for retrieving additional values from a chosen variable in Angular 10

Here is an array I have: export const Glcode = [ { id: 1, Type: 'Asset', Name: 'Cash at Head Office', code: '10018' }, { id: 2, Type: 'Asset', Name: 'POS ACCOUNT ', code: '10432' }, { ...

Issue with const declaration in Typescript and Node.js - initializer is missing

Encountering a syntax error with the following line of code. Error message: SyntaxError: Missing initializer in const declaration const dataMap : Map<string, any> = new Map(); ...

Trigger the browser to refresh translation files following the deployment

Our Angular/Ionic app utilizes the ngx-translate/core package for translations, and is hosted on Firebase. With each new build and deployment, Angular automatically creates a hash for our js files to ensure the browser fetches the latest version when chang ...

Learn how to implement React Redux using React Hooks and correctly use the useDispatch function while ensuring type-checking

I'm curious about the implementation of Redux with Typescript in a React App. I have set up type-checking on Reducer and I'm using useTypedSelector from react-redux. The only issue I have is with loose type-checking inside the case statements of ...

Create a .exe file from the ordered test for export

Currently, I am working on testing the user interface of my web application in VB.Net. My preferred tool for this is Selenium. I have created an ordered test and now I am looking to export it as a .exe file. However, when I compile my test unit project in ...

Utilizing Angular 10 to Transform a JSON Data into a Custom String for HTML Rendering

I have received a JSON response from my backend built using Spring Boot. The "category" field in the response can either be 1 or 2, where 1 represents Notifications and 2 represents FAQs. { "pageNumber": 0, "size": 5, "totalPages&q ...