JPS
JPS

Reputation: 2760

Angular Unit Testing - Mock async calls of injected services with TestBed

I need to write unit tests for the following DataService ,

@Injectable()
export class DataService {
   constructor(private config: ConfigService, private http: HttpClient) {

   }

   .....

   someMethod(){
        let apiUrl = this.config.get('api').url;   // LINE 1
   }
}

The ConfigService is injected to DataService that has a load function which gets the configuration from a json file. This load function will be invoked when the app is initialized.

export function configServiceFactory(config: ConfigService) {
    return () => config.load();
}

...

providers: [
        ConfigService,
        {
            provide: APP_INITIALIZER,
            useFactory: configServiceFactory,
            deps: [ConfigService],
            multi: true
        }
    ]

Here's a part of data-service.spect.ts file,

...

beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        HttpClientTestingModule
      ],
      providers: [DataService, ConfigService]
    });

    mock = TestBed.get(HttpTestingController);
    service = TestBed.get(DataService);
  });

....

so when i run the test, in LINE 1 i get that the this.config.get('api') is undefined. I can understand that it is because the ConfigService did not load the data from JSON. So now how i can i make the injected services also make the async calls during unit tests?

Upvotes: 3

Views: 4478

Answers (1)

Bunyamin Coskuner
Bunyamin Coskuner

Reputation: 8859

When writing unit tests, you would want to mock every dependency you have. You already do this for HttpClient by importing HttpClientTestingModule so you need to do the same for ConfigService.

There are two ways to do so.

1- Fake Service (Stub)

export class ConfigServiceStub {
    get(input: string) {
        // return static value instead of calling an API
    }
}

...

beforeEach(() => {
    TestBed.configureTestingModule({
        imports: [
            HttpClientTestingModule
        ],
        providers: [DataService, 
                   {provide: ConfigService, useClass: ConfigServiceStub}
        ]
    });

    mock = TestBed.get(HttpTestingController);
    service = TestBed.get(DataService);
});

With this way, your DataService won't call the real ConfigService, but instead it'll call the get method of ConfigServiceStub. When using Stubs, you don't need to worry about other dependencies ConfigService has. You just implement the methods that you want to override.

2-Spies

You can create spies on the method that you don't want to invoke.

it('run some test', inject([DataService], (service: DataService) => {
    spyOn(service.config, 'get').and.returnValue('someString');

    // run your tests here
});

Even though, ConfigService.get method won't get called in the example above, Angular still needs to create an instance of ConfigService which may be hard to do so in some examples or may result in creating too many other services for simple test.

I'd go with option 1

For more information on spies, check here

Upvotes: 3

Related Questions