Home > Software design >  Angular tests failing after using APP_INITIALIZER to load config file
Angular tests failing after using APP_INITIALIZER to load config file

Time:06-11

I am new to Angular so please excuse maybe simple question. I wrote an app, that loads config json using HttpClient and APP_INITIALIZER, but now all my tests are failing with message: TypeError: Cannot read properties of undefined (reading 'theme'). Can you please point me the right direction?

app.component.spec.ts:

import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';

describe('AppComponent', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [RouterTestingModule],
      declarations: [AppComponent],
    }).compileComponents();
  });

  it('should create the app', () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.componentInstance;
    expect(app).toBeTruthy();
  });
});

app-config.service.ts:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { firstValueFrom } from 'rxjs';

@Injectable({
  providedIn: 'root',
})
export class AppConfigService {
  static config: IAppConfig;

  constructor(private http: HttpClient) {}

  loadConfig() {
    return new Promise<void>(async (resolve, reject) => {
      try {
        const get$ = this.http.get('assets/app.config.json');
        const configFile = await firstValueFrom(get$);
        AppConfigService.config = <IAppConfig>configFile;
        resolve();
      } catch (e) {
        console.error('Error loading config file', e);
        reject(e);
      }
    });
  }
}

export interface IAppConfig {
  theme: string;
}

app.module.ts

import { HttpClientModule } from '@angular/common/http';
import { NgModule, APP_INITIALIZER } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppConfigService } from './app-config.service';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

// Function to load application config on application startup
export function initializeApp(appConfigService: AppConfigService) {
  return (): Promise<any> => {
    return appConfigService.loadConfig();
  };
}

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, AppRoutingModule, HttpClientModule],
  providers: [
    AppConfigService,
    {
      provide: APP_INITIALIZER,
      useFactory: initializeApp,
      deps: [AppConfigService],
      multi: true,
    },
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}

app.component.ts

import { Component } from '@angular/core';
import { AppConfigService } from './app-config.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent {
  theme = AppConfigService.config.theme;
}

CodePudding user response:

you have a few issues, first you should not use the class for setting properties. The service should declare some properties, and inside this service, you could set this.config = someValue.

Then, if you want to use the Service in a component, you have to inject it, basically with the constructor through dependency injection : https://angular.io/guide/dependency-injection

constructor(heroService: HeroService)

And then you would add the service - preferably with mocking - as a provider in your test file. You acheive that with Spy from Jasmine like so :

https://codecraft.tv/courses/angular/unit-testing/mocks-and-spies/

Also, I would drop Promises for Observables that are much more powerful. Cheers !

CodePudding user response:

So I made some changes and this is what I came with. It is working and all test are okay as well. No need for APP_INITIALIZE as well. Do you see any problems now?

app.component.spec.ts:

import { HttpClientTestingModule } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';

describe('AppComponent', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [RouterTestingModule, HttpClientTestingModule],
      declarations: [AppComponent],
    }).compileComponents();
  });

  it('should create the app', () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.componentInstance;
    expect(app).toBeTruthy();
  });
});

app-config.service.ts:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root',
})
export class AppConfigService {
  
  constructor(private http: HttpClient) {}

  loadConfig() {
    return this.http.get<IAppConfig>('assets/app.config.json');
  }
}

export interface IAppConfig {
  theme: string;
}

app.module.ts:

import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, AppRoutingModule, HttpClientModule],
  bootstrap: [AppComponent],
})
export class AppModule {}

app.component.ts:

import { Component } from '@angular/core';
import { AppConfigService, IAppConfig } from './app-config.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent {
  config: IAppConfig = {
    theme: 'blue'
  };

  constructor(appConfigService: AppConfigService) {
    appConfigService.loadConfig().subscribe(data => this.config = data)
  }
}
  • Related