Testing Angular Components - Creating Mocks for AngularFireStore Collection Fetching

Update I made some progress today, but I've encountered a new error in the same process. Updated question below.

I'm here with another query regarding how to simulate a complex call from AngularFireStore.

I'm facing an issue while running my tests with the importData function in my service. The final get call should return an array that I need to verify, but I'm struggling to set up the mock correctly, causing the test to fail.

Below is the Version 2 of my mock file, which I believe is very close to working. I've resolved all errors related to various functions, and currently, I'm getting stuck at the first IF statement that checks if the returned array's length is 0 or not. The return is unfortunately 'undefined', leading to the error.

Interestingly, when the final return in my mock is changed to return Promise.resolve() (without an array inside the parentheses), the error changes to:

Error: Uncaught (in promise): TypeError: Cannot read properties of undefined (reading 'docs')
. Adding the array then triggers an error related to the undefined 'length'.

I'm also considering if this issue is related to processing and fakeasync, so I've experimented with a few versions. Currently, the test uses 'fakeasync'. Another point worth noting is that changing it from 'fakeasync' to just 'async' with an await or without results in a 'pass' for the test but still throws the error in the console.

TypeError: Error: Uncaught (in promise): TypeError: Cannot read properties of undefined (reading 'length') TypeError: Cannot read properties of undefined (reading 'length')

service.ts

importData(Id: number) {

    this.afs.collection<data>('users/' + this.authService.liveUid + .ref.where('id', '==', Id).get().then(
      a => {

        if (a.docs.length == 0) {

          if (validID(Id, 8, 9)) {
            this.http.get<DataImport>('https://url' + Id).subscribe(

         //do stuff

            )
          } else 
          {
            this.urlError = true
            this.spinnerImported = true
          }

        }
        else {
          this.spinnerImported = true
          this.sameImportName = a.docs[0].get('name')
        }
      }
    )
  }

service.spec.ts

describe('ImportService', () => {
  let service: ImportService;

  beforeEach(() => {
    TestBed.configureTestingModule(
      {
        providers: [
          { provide: HttpClient, useValue: httpClientMock },
          { provide: AngularFirestore, useValue: AngularFirestoreMock },
          { provide: AuthService, useValue: AuthServiceMock },
        ]
      });
    service = TestBed.inject(ImportService);
  });

  it('should be created', fakeasync (() => {

    service.importData(32198712)
    tick ()
  }));
});

**AngularFirestoreMock (version 1) **

export const AngularFirestoreMock = jasmine.createSpyObj('AngularFirestoreMock', {
  'doc': {
    set: function () {
      return Promise.resolve()
    }
  },
  'collection':{
    get: function () {
      return Promise.resolve()
    }
  }

}
)

AngularFirestoreMock (version 2)

export const AngularFirestoreMock = jasmine.createSpyObj('AngularFirestoreMock', {
  'doc': {
    set: function () {
      return Promise.resolve()
    }
  },
  'collection': {
    'ref': {
      where: () => {
        return {
          get: () => {
            return Promise.resolve([{ name: 'test'}])
          }
        }
      }
    }
  }})

Answer №1

My attempt was almost there, but I overlooked the fact that the response from the final Promise.resolve() needed to include the 'doc's property containing the array being evaluated.

Currently, my test is entering the 'else' part of the first 'if' statement because my default 'AngularFireStoreMock' is returning an array with a length greater than 0. For future tests, I will use a returnValue to set it to null in order to trigger the other part of the if/else statement.

This situation led me to discover another function that needed to be mocked - the 'get' function from the returned docs array at position [0].

AngularFireStore Mock

export const AngularFirestoreMock = jasmine.createSpyObj('AngularFirestoreMock', {
  'doc': {
    set: function () {
      return Promise.resolve()
    }
  },
  'collection': {
    'ref': {
      where: () => {
        return {
          get: () => {
            return Promise.resolve({
              'docs': [
                {
                  get: () => {
                    return { name: 'Umphrii Fieldstone' }
                  }
                }
              ]
            })
          }
        }
      }
    }
  }
})

Analysis I gained valuable insights from unraveling this issue, so for anyone who may benefit, here is my thought process and understanding:

The call to be mocked:

this.afs.collection<data>('users/' + this.authService.liveUid + .ref.where('id', '==', Id).get()

This call involves the following methods and properties, which I was able to identify by hovering over them in VSCode and discerning their types as method or property to construct the mock accordingly.

  • collection (method)
  • ref (property)
  • where (method)
  • get (method)

It's important to note that jasmine.createSpyObj uses the first key in the object as the initial function/method (in this case 'collection'). Subsequently, you can utilize the appropriate syntax for properties or methods. The final get() call returns data within the promise.resolve() because it's subscribed to with a .then.

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

What is the best way to reload a React/TypeScript page after submitting a POST request?

I am working on a custom plugin for Backstage that interacts with Argo CD via API calls. To retrieve application information, I make a GET request to the following endpoint: https://argocd.acme.com/api/v1/applications/${app-name} If the synchronizati ...

Typescript: Issue encountered with Record type causing Type Error

When utilizing handler functions within an object, the Record type is used in the following code snippet: interface User { id: string; avatar: string; email: string; name: string; role?: string; [key: string]: any; } interface Stat ...

Dealing with CORS policy challenge

I am encountering an issue with Access to XMLHttpRequest at 'http://demo.equalinfotech.com/Iadiva/images/product/1634623781ladiva_barbara_01.glb' from origin 'http://localhost:8100' being blocked by CORS policy due to the absence of the ...

What could be causing Angular to mistakenly refer to a clearly defined object as undefined?

Within my ngrx reducer, I establish the initial state in the following manner: const initialState = { homeState: { banner: { images: ['142_111421_HOMEPAGE_HERO-SLIDE_1_DESKTOP_EN.jpg', '5434424542.jpg'], } } } When it ...

Store the video file transmitted through a multipart form in Serverless Offline mode

I currently have a website built with Angular4 featuring a basic form for uploading data using ng2-file-upload. The files are sent to a Node.js-based serverless offline server where my goal is to simply save those files received from the form onto disk. A ...

Angular 2: Firebase fails to provide a response

I am facing an issue with retrieving data from my Firebase using Angular 2 and TypeScript. While console.log() works, I am unable to return the value into a variable. My DataService looks like this: import {Injectable} from "angular2/core"; import ' ...

What is the solution for resolving this Angular issue: Expected argument expression.ts(1135)?

While following a CRUD tutorial, I encountered an issue with the code. Even though I have verified that my code matches the tutorial's code, I am getting an error message saying "Argument expression expected. ts(1335)" in the submit method onSubmit(). ...

Guide on how to refresh a child component in Angular after a function yields a fresh value?

Two main elements are at play here: Parent and Child. parent.ts class Parent { state = 0; ... }; parent.html <child [state]='state'> </child> Whenever the state in Parent is modified, the child component reflects the change ...

The index access type cannot be used with T[Key extends keyof T]

My work frequently involves arrays structured like this: [ {key1: val1, key2: value2, key3: val3}, {key1: val1, key2: value2, key3: val3}, {key1: val1, key2: value2, key3: val3}] and I often need to convert them into a dictionary/map format, for example: ...

Can a subclass or interface delete an inherited field or method from its parent class?

Here's a scenario: interface A { a: number; x: any; } interface B extends A { b: number; } interface C { a: number; b: number; } Can we make B equal to C (excluding the x field but still extending A)? If it is possible, then how can we a ...

How can I successfully transmit the entire event during the (change) event binding with ng-select in Angular 14?

I'm working on Front end Code <ng-select formControlName="constituencyId" placeholder="Select Constituency" (change)='onContituencyChanged($event)'> > &l ...

Guidelines for transitioning an AngularJS module for injection into an Angular 2 component

After diving into the Angular 2 upgrade guide and successfully setting up a hybrid app (combining ng1 as a base code with components and services gradually transitioning to ng2), I've hit a snag. How do I incorporate 3rd party ng1 modules so that Angu ...

How can I retrieve the /api/auth/me resource serverside using the NextJS AppRouter?

I am looking to implement app router in my Next.js project and have encountered an issue. In order for my app to function properly, I need to make a call to /api/auth/me which will return either a user object or null if the user is not logged in. To achiev ...

Advanced type generics in Typescript

I've hit a roadblock in my attempt to ensure type safety for a function that should take an object and return a number. Despite numerous efforts, I haven't been successful. To give you a simple example (the actual application involves more comple ...

Ways to access the types of function parameters

Obtaining a method's parameter types using ReflectAPI is simple: Reflect.getMetadata('design:paramtypes', target, propertyKey); However, the challenge arises when trying to retrieve a function's parameter types as it constantly return ...

An effective way to pass an array as data to an express router from Angular

I've been attempting to retrieve more detailed information for multiple ID's from the database, but I've hit a roadblock. Below is the array of member ID's: var memberIds = ["2892056", "2894544", "2894545" ...

Monitor changes in input display within ngIf statements

Within my code, I am utilizing an input element within a conditional statement of *ngIf: <div *ngIf='display'> <input number="number" /> </div> My goal is to be able to detect the visibility state of the input element insi ...

Error in TypeScript: Objects can only specify properties that are known, and 'state' is not found in type 'Partial<Path>'

As I strive to pass props through a React Router Link, my goal is to include all the user props. The code below is causing an error, particularly where state: {...employee} is highlighted. Although I am relatively new to TypeScript, I am actively working ...

Error Type: TypeError when using Mongoose's FindOneAndUpdate function

I am encountering difficulties while trying to implement a findOneAndUpdate query. //UserController UserDAO ['findOneAndUpdate'](userId, {& ...

Should I opt for 'typeof BN' or the BN Constructor when working with TypeScript and "bn.js"?

Note: Despite the recommendation to use BigInts instead of bn.js, I am currently working with a legacy codebase that has not been migrated yet. Below is the code that compiles and executes without any issues: import { BN } from "bn.js"; import a ...