"Exploring the Depths of Angular with Google Maps and Karma

After upgrading an Angular project from version 8 to 11 and updating the dependencies, I encountered an issue with compatibility. The project previously used the @agm/core package, which is not compatible with Angular 11. I replaced it with @angular/google-maps as suggested, and although everything works fine, the tests are failing.

The map is a part of a component where the Google API is loaded, ensuring it is not loaded multiple times:

import { Component, OnInit, ViewChild } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { environment } from 'src/environments/environment';
import { GoogleMap } from '@angular/google-maps';

@Component({
  selector: 'app-map',
  templateUrl: './map.component.html',
  styleUrls: ['./map.component.scss']
})
export class MapComponent implements OnInit {

  @ViewChild(GoogleMap, { static: false }) map: GoogleMap;

  apiLoaded: Observable<boolean>;

  constructor(httpClient: HttpClient) {
    if (window.google === undefined) {
      this.apiLoaded = httpClient.jsonp(`https://maps.googleapis.com/maps/api/js?key=${environment.googleAPIKey}`, 'callback')
          .pipe(
            // Doing preparation stuff
          );
    }
    else {
      this.apiLoaded = of(true);
    }
  }

}

While everything works as intended when running ng serve, running ng test presents issues:

Unhandled promise rejection: InvalidValueError: ng_jsonp_callback_0 is not a function
error properties: null({ constructor: Function })
Error: 
    at new ge (https://maps.googleapis.com/maps/api/js?key=<api-key>&callback=ng_jsonp_callback_0:70:72)
    at Object._.he (https://maps.googleapis.com/maps/api/js?key=<api-key>&callback=ng_jsonp_callback_0:70:182)
    at Nj (https://maps.googleapis.com/maps/api/js?key=<api-key>&callback=ng_jsonp_callback_0:146:233)
    at https://maps.googleapis.com/maps/api/js?key=<api-key>&callback=ng_jsonp_callback_0:146:118
    at ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone-evergreen.js:364:1)
    at Zone.run (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone-evergreen.js:123:1)
    at http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone-evergreen.js:857:1
    at ZoneDelegate.invokeTask (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone-evergreen.js:399:1)
    at Zone.runTask (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone-evergreen.js:167:1)
    at drainMicroTaskQueue (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone-evergreen.js:569:1)

It seems that the Google API is not present during testing. Various attempts were made to solve this issue, including:

  • Adding the Google API loading in the index.html:
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY"></script>
  • Adding the link to the Karma config file in the files section:
    files: [
      'https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY'
    ],
  • Downloading the link's content, putting it into a file, and adding the referenced file in the Karma config under the files section. Although this works initially, it soon throws errors, making it unsuitable for automated testing on Jenkins.
  • Trying online mocks, but the existing tests heavily utilize functions that don't work with a mocked API.
  • Attempting to load the API in the test file before running the tests, but this also did not yield the expected results:
  beforeEach(waitForAsync(() => {
    httpClient.jsonp(`https://maps.googleapis.com/maps/api/js?key=${environment.googleAPIKey}`, 'callback');

While considering moving the API loading into a service or pre-loading the API in the Karma config file before testing, it seems like there isn't an elegant solution yet.

How can I ensure the current Google Maps API is present for testing in a more seamless manner?

Answer №1

I have resolved the issue by implementing a pretest script:

package.json:

//...
  "scripts": {
    //...
    "pretest": "./pretest.sh",
    "test": "ng test",
//...

The script ensures that the API code is loaded before running the tests (pretest.sh):

echo "Executing 'pretest.sh' script to fetch Google Maps API for testing..."
rm google-maps-api.js
date +"// Download time: %Y-%m-%d %H:%M" >> google-maps-api.js
curl 'https://maps.googleapis.com/maps/api/js?key=<API_KEY>' >> google-maps-api.js
echo "'pretest.sh' execution complete"

Karma is configured to include the API file for testing in the karma.config.js files section:

module.exports = function (config) {
  config.set({
    basePath: '',
    plugins: [
      //...
    ],
    files: [
      'google-maps-api.js'
    ],

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

Strange occurrences with HTML image tags

I am facing an issue with my React project where I am using icons inside img tags. The icons appear too big, so I tried adjusting their width, but this is affecting the width of other elements as well. Here are some screenshots to illustrate: The icon wit ...

Creating connections between different services and components

I'm having an issue with importing my service into my component as it's giving me a "cannot find module" error. Below is the code from selectHotel.component.ts import { Component } from '@angular/core'; import { GetHotelService } from ...

Can you point me to the source of definition for Vue 2's ComponentDefinition and ComponentConstructor types?

I am struggling to add a dynamic Vue 2 component with correct typing in TypeScript. The documentation clearly mentions that the is attribute accepts values of type string | ComponentDefinition | ComponentConstructor, but I cannot locate these custom types ...

Unable to perform module augmentation in TypeScript

Following the guidelines provided, I successfully added proper typings to my react-i18next setup. The instructions can be found at: However, upon creating the react-i18next.d.ts file, I encountered errors concerning unexported members within the react-i18 ...

Verification based on conditions for Angular reactive forms

I am currently learning Angular and working on creating a reactive form. Within my HTML table, I have generated controls by looping through the data. I am looking to add validation based on the following cases: Upon page load, the Save button should be ...

Set the component variable to hold the output of an asynchronous method within a service

As I work on developing an application, I aim to keep my component code concise and devoid of unnecessary clutter. To achieve this, I plan to offload complex logic into a service which will then be injected into the component. Suppose my component includes ...

Steps to create an instance method that only accepts the name of another instance method

I am looking to enhance an object by adding a method that specifically accepts the name of another method within the object. How can I achieve this in a way that dynamically narrows down the accepted names of methods, without hardcoding them? Let's t ...

Utilize the pipe function to generate a personalized component

I have incorporated a custom pipe in my Angular 2 application to parse and make URLs clickable within messages displayed using an ngFor loop. If the URL links to a YouTube video, I also convert it into embed code. To optimize performance, I am looking to ...

Ion-List seamlessly integrates with both ion-tabs and ion-nav components, creating a cohesive and dynamic user interface

On my homepage, there is an ion-list. Sometimes (not every time), when I select an item in this list or navigate to the register page using "this.app.getRootNav().push("ClienteCadastroPage")", and then select an input in the registerPage or descriptionPage ...

Issue with Angular 7: In a ReactiveForm, mat-select does not allow setting a default option without using ngModel

I have a Angular 7 app where I am implementing some reactive forms. The initialization of my reactive form looks like this: private initFormConfig() { return this.formBuilder.group({ modeTransfert: [''], modeChiffrement: [' ...

Tips for waiting for an observable loop

When using my application, the process involves uploading a series of images in order to retrieve the file IDs from the system. Once these IDs are obtained, the object can then be uploaded. async uploadFiles(token: string):Promise<number[]> { let ...

Encountering the ExpressionChangedAfterItHasBeenCheckedError error during Karma testing

Testing out some functionality in one of my components has led me to face an issue. I have set up an observable that is connected to the paramMap of the ActivatedRoute to retrieve a guid from the URL. This data is then processed using switchMap and assigne ...

Adding typing to Firebase Functions handlers V2: A step-by-step guide

Here's a function I am currently working with: export async function onDeleteVideo(event: FirestoreEvent<QueryDocumentSnapshot, { uid: string }>): Promise<any> { if (!event) { return } const { disposables } = event.data.data() ...

The specified path is not found within the JsonFilter

Something seems off. I'm using Prisma with a MongoDB connection and attempting to search the JSON tree for specific values that match the [key, value] from the loop. However, I haven't made much progress due to an error with the path property. Be ...

In Typescript, ambient warnings require all keys in a type union to be included when defining method parameter types

Check out this StackBlitz Example Issue: How can I have Foo without Bar, or both, but still give an error for anything else? The TypeScript warning is causing confusion... https://i.stack.imgur.com/klMdW.png index.ts https://i.stack.imgur.com/VqpHU.p ...

The Angular Http Interceptor is failing to trigger a new request after refreshing the token

In my project, I implemented an HTTP interceptor that manages access token refreshing. If a user's access token expires and the request receives a 401 error, this function is designed to handle the situation by refreshing the token and re-executing ...

What is the proper way to define the scope for invoking the Google People API using JavaScript?

I am attempting to display a list of directory people from my Google account. export class People { private auth: Auth.OAuth2Client; private initialized: boolean = false; private accessToken: string; constructor(private readonly clientEmail: strin ...

Error: Couldn't locate Next.js - TypeScript module

I encountered an error with the image, but I am unsure of the reason behind it. Additionally, the directory is included in the second image. https://i.sstatic.net/knUzH.png import Link from 'next/link'; import { useState } from 'react' ...

Angular2 application encounters issue with target attribute being overlooked by Chrome

I am trying to implement a link in an iframe following the instructions on W3Schools: <iframe height="300px" width="100%" src="demo_iframe.htm" name="iframe_a"> </iframe> <p><a href="http://www.w3schools.com" target="iframe_a">W3S ...

How can one determine the completion of a chunked download request in Angular's HTTP client?

Currently, I am utilizing angular's HttpClient to retrieve an arraybuffer. The server is transmitting the data along with the following headers: *To avoid any confusion, the download route essentially retrieves a chunk file stored in the cloud. Howev ...