NullInjectorError while testing

Issue

I have the following problem while running ng test in Angular 12:

NullInjectorError: R3InjectorError(DynamicTestModule)[BaseURL ->
BaseURL]: NullInjectorError: No provider for BaseURL! error
properties: Object({ ngTempTokenPath: null, ngTokenPath: [ ‘BaseURL’,
‘BaseURL’ ] }) NullInjectorError:
R3InjectorError(DynamicTestModule)[BaseURL -> BaseURL]:
NullInjectorError: No provider for BaseURL!
at NullInjector.get (http://localhost:9876/karma_webpack/webpack:/node_modules/@angular/core/ivy_ngcc/fesm2015/core.js:11101:1)
at R3Injector.get (http://localhost:9876/karma_webpack/webpack:/node_modules/@angular/core/ivy_ngcc/fesm2015/core.js:11268:1)
at R3Injector.get (http://localhost:9876/karma_webpack/webpack:/node_modules/@angular/core/ivy_ngcc/fesm2015/core.js:11268:1)
at NgModuleRef$1.get (http://localhost:9876/karma_webpack/webpack:/node_modules/@angular/core/ivy_ngcc/fesm2015/core.js:25332:1)
at Object.get (http://localhost:9876/karma_webpack/webpack:/node_modules/@angular/core/ivy_ngcc/fesm2015/core.js:25046:1)
at lookupTokenUsingModuleInjector (http://localhost:9876/karma_webpack/webpack:/node_modules/@angular/core/ivy_ngcc/fesm2015/core.js:3342:1)
at getOrCreateInjectable (http://localhost:9876/karma_webpack/webpack:/node_modules/@angular/core/ivy_ngcc/fesm2015/core.js:3454:1)
at ɵɵdirectiveInject (http://localhost:9876/karma_webpack/webpack:/node_modules/@angular/core/ivy_ngcc/fesm2015/core.js:14737:1)
at NodeInjectorFactory.MenuComponent_Factory [as factory] (ng:///MenuComponent/ɵfac.js:5:7)
at getNodeInjectable (http://localhost:9876/karma_webpack/webpack:/node_modules/@angular/core/ivy_ngcc/fesm2015/core.js:3549:1)

Error: Expected undefined to be truthy.
at
at UserContext. (http://localhost:9876/karma_webpack/webpack:/src/app/menu/menu.component.spec.ts:46:23)
at ZoneDelegate.invoke (http://localhost:9876/karma_webpack/webpack:/node_modules/zone.js/fesm2015/zone.js:372:1)
at ProxyZoneSpec.onInvoke (http://localhost:9876/karma_webpack/webpack:/node_modules/zone.js/fesm2015/zone-testing.js:287:1)

The code for the spec.ts is:

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MenuComponent } from './menu.component';
import { DishService } from '../services/dish.service';

describe('MenuComponent', () => {
  let component: MenuComponent;
  let fixture: ComponentFixture<MenuComponent>;

const mockDishService = {
  getDishes: () => {
     return {
        id: '000',
        name: 'nnnnn'
     }
  }
}

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [],
      declarations: [ MenuComponent ],
      providers: [
        { provide: DishService, useValue: mockDishService },
      ]
    })
    .compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(MenuComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();

  });

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

});

The code of the component is:

import { Component, OnInit, Inject } from '@angular/core';
import { Dish } from '../shared/dish';
import { DishService } from '../services/dish.service';
import { flyInOut, expand } from '../animations/app.animation';

@Component({
  selector: 'app-menu',
  templateUrl: './menu.component.html',
  styleUrls: ['./menu.component.scss'],
  // tslint:disable-next-line:use-host-property-decorator
  host: {
    '[@flyInOut]': 'true',
    'style': 'display: block;'
    },
  animations: [
    flyInOut(),
    expand()
  ]
})
export class MenuComponent implements OnInit {

  dishes!: Dish[];
  errMess: string;
  
  constructor(private dishService: DishService, 
    @Inject ('BaseURL') public baseURL) { }

  ngOnInit(): void {
    this.dishService.getDishes().subscribe((dishes => this.dishes = dishes), errMess => this.errMess = <any>errMess);
  }

}

I’m using the baseURL to obtain the information for a db.json file while running json-server –watch db.json to simulate that I get the information from the server, so I have a shared .ts file named baseurl.ts with the following code:

export const baseURL = 'http://localhost:3000';

And in the app.module.ts I import the const

import { baseURL } from './shared/baseurl';

and I add it as a provider in the same app.module.ts file:

providers: [
    { provide: 'BaseURL', useValue: baseURL }
  ],

I will appreciate someone is help on this error

Solution

Issue 1: NullInjectorError

This error message shows as your DishComponent requires BaseURL to be injected [@Inject ('BaseURL')].

NullInjectorError: R3InjectorError(DynamicTestModule)[BaseURL -> BaseURL]: NullInjectorError: No provider for BaseURL!

Solution for Issue 1

Ensure that you add BaseURL in providers section for unit testing.

const baseUrl = "your Base URL";

beforeEach(async () => {
  await TestBed.configureTestingModule({
    ...
    providers: [
      { provide: DishService, useValue: mockDishService },
      { provide: 'BaseURL', useValue: baseUrl },
    ]
  })
  .compileComponents();
});

Issue 2: Return wrong value for mockDishService.getDishes()

From MenuComponent, it is expected that the dishService.getDishes() return the value of Observable<Menu[]> or Observable<any[]> type.

dishes!: Dish[];

this.dishService.getDishes().subscribe((dishes => this.dishes = dishes), errMess => this.errMess = <any>errMess);

While the mockDishService.getDishes() returns the value of any or Menu type, which the data returned was unmatched.

const mockDishService = {
  getDishes: () => {
    return {
      id: '000',
      name: 'nnnnn',
    };
  },
};

Hence you will get this error message as below:

TypeError: this.dishService.getDishes(…).subscribe is not a function

Solution for Issue 2:

Ensure that the mockDishService.getDishes() returns value of Observable<Menu[]> or Observable<any[]> type to match with real dishService.getDishes().

import { of } from 'rxjs';

const mockDishService = {
  getDishes: () => {
    return of([
      {
        id: '000',
        name: 'nnnnn',
      },
    ]);
  },
};

Sample Solution on StackBlitz

Answered By – Yong Shun

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published