pjlamb12
pjlamb12

Reputation: 2422

Angular-Testing: How to provide configuration object to service class with TestBed?

I have an Angular service that requires a config object be passed in to the service:

// my.module.ts
@NgModule({ ... })
export class MyModule {
    static forRoot(config: MyServiceConfig): ModuleWithProviders {
        return {
            ngModule: MyModule,
            providers: [{ provide: MyServiceConfig, useValue: config }],
        };
    }
}


//my.service.ts
export class MyService {
        constructor(private _http: HttpClient, @Optional() config: MyServiceConfig) {
        if (config) {
            if (!config.attr1) {
                throw new Error('You must provide the attr1 to use this Module.');
            } else if (!config.attr2) {
                throw new Error('You must provide the attr2 to use this Module.');
            } else {
                this.attr1 = config.attr1;
                this.attr2 = config.attr2;
            }
        } else {
            throw new Error(
                'You must provide a MyServiceConfig object with the attr1 and the attr2 to use this module.',
            );
        }
    }

}

This all works, but I'd like to write a couple tests around providing that config object to the service. I had the following beforeEach in the test file, and it threw an error as expected when the config object wasn't provided:

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

But when I tried to move that out of the beforeEach and into an individual test, I couldn't get the error to throw properly. If it was called exactly as above but in a test, it would:

it('should do something', () => {
    TestBed.configureTestingModule({
        imports: [HttpClientTestingModule],
        providers: [FeedbackService],
    });
});

I tried the above in a try/catch block, trying to catch the error, but it gave me a false positive. I tried the expect(() => {}).toThrowError() and toThrow() methods, but even when putting the TestBed.configureTestingModule() inside that arrow function in the expect didn't work. It doesn't throw an error when done that way.

Is there a way to do this? Also, is there a way to provide the configuration object to the service to test that it sets the service attributes to the correct values?

Upvotes: 1

Views: 1927

Answers (2)

pjlamb12
pjlamb12

Reputation: 2422

I used some of @Jota.Toledo's answer and edited to get the following test file:

import { TestBed } from '@angular/core/testing';

import { MyService } from './my.service';
import { MyServiceConfig } from './my-service-config';

describe('MyService', () => {
    beforeEach(() => {
        TestBed.configureTestingModule({
            imports: [HttpClientTestingModule],
            providers: [MyService],
        });
    });

    describe('config object provided', () => {
        let config: MyServiceConfig;
        const attr1 = 'https://my-test-api.test.com';
        const attr2 = 'testing';

        beforeEach(() => {
            config = null;
        });

        it('should use the values passed in the config for the attr1 and attr2', () => {
            config = { attr1, attr2 };
            TestBed.overrideProvider(MyService, { useFactory: () => new MyService(null, config) });
            const service: MyService = TestBed.get(MyService);

            expect(service.attr1).toBe(attr1);
            expect(service.attr2).toBe(attr2);
        });

        it('should throw an error if config object is provided but not the attr1 attribute', () => {
            try {
                config = { attr1: null, attr2 };
                TestBed.overrideProvider(MyService, { useFactory: () => new MyService(null, config) });
                const service: MyService = TestBed.get(MyService);
            } catch (e) {
                expect(e.message).toBe('You must provide the api URL to use this module.');
            }
        });

        it('should throw an error if config object is provided but not the attr2 attribute', () => {
            try {
                config = { attr1, attr2: null };
                TestBed.overrideProvider(MyService, { useFactory: () => new MyService(null, config) });
                const service: MyService = TestBed.get(MyService);
            } catch (e) {
                expect(e.message).toBe('You must provide the feedback source to use this module.');
            }
        });
    });

    describe('config object not provided', () => {
        beforeEach(() => {
            TestBed.overrideProvider(MyService, { useFactory: () => new MyService(null, null) });
        });
        it('should throw an error if no config object provided', () => {
            try {
                const service: MyService = TestBed.get(MyService);
            } catch (e) {
                expect(e.message).toBe(
                    'You must provide a MyServiceConfig object with the attr1 and the attr2 to use this module.',
                );
            }
        });
    });
});

This properly threw errors when it was supposed to, and I was able to check the message property to make sure that it threw the correct error at the correct time.

Upvotes: 0

Jota.Toledo
Jota.Toledo

Reputation: 28434

Simply provide a value for the config object:

describe("FeedbackService", ()=>{
    beforeEach(() => {
        TestBed.configureTestingModule({
            imports: [HttpClientTestingModule],
            providers: [FeedbackService]
        });
    });

    describe("when config object is provided", ()=>{
        let dummyConfig : Object;
        beforeEach(()=>{
          dummyConfig = {/* set some properties*/};
          TestBed.overrideProvider(MyServiceConfig, {useValue: dummyConfig});
        });

        it("should not explode", ()=>{
          // a test in which the config object is dummyConfig
        });
    });
});

Sidenote: I dont see the point of decorating the config object with @Optional and throw when no value for the token is provided. You are basically re-implementing the default not-provided logic.

Upvotes: 1

Related Questions