Angular Testing - Mocking An InjectionToken

元气小坏坏 提交于 2020-05-29 10:22:29

问题


I'm trying to test an Angular service that handles SignalR connections, which takes the code for SignalR as an InjectionToken.

Here's the provider file:

// signalr-provider.ts

import { InjectionToken } from '@angular/core';

export const SIGNALR_TOKEN = new InjectionToken('signalR');

export function signalRFactory() {
    return window['signalR'];
}

export const SIGNALR_PROVIDER = [ { provide: SIGNALR_TOKEN, useFactory: signalRFactory } ];

And here's the service:

// signalr-service.ts

import { Injectable, Inject } from '@angular/core';

import { SIGNALR_TOKEN } from './signalr-provider';
import { HubConnection } from '@aspnet/signalr';
import { environment } from '../../../environments/environment';

@Injectable()
export class SignalrService {
    private hubConnection: HubConnection;
    private baseUrl: string = environment.baseUrl;

    constructor(@Inject(SIGNALR_TOKEN) private signalR: any) {
        this.init();
    }

    init(): void {
        this.hubConnection = new this.signalR.HubConnectionBuilder().withUrl(`${this.baseUrl}/hubs/test`).build();
    }
}

The token is provided in the app module like this:

// From app.module.ts

@NgModule({
    declarations: [...],
    imports: [...],
    providers: [ SIGNALR_PROVIDER, SignalrService ],
    bootstrap: [ AppComponent]
})
export class AppModule {}

So far, I'm having no luck trying to mock the InjectionToken in my test file, and end up with a NullInjectorError: No provider for InjectionToken signalR! message every time.

Here's my first attempt, returning a mocked object from the signalRFactory function with only the properties needed in the initial connection:

// signalr-service.spec.ts

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

import { SignalrService } from './signalr-service';

const SIGNALR_TOKEN = new InjectionToken('signalR');

function signalRFactory() {
    return {
        HubConnectionBuilder: () => {
            return {
                withUrl: (url) => {
                    return {
                        build: () => {}
                    };
                }
            };
        }
    };
}

const SIGNALR_PROVIDER = [ { provide: SIGNALR_TOKEN, useFactory: signalRFactory } ];

describe('ConnectionService', () => {
    beforeEach(
        async(() => {
            TestBed.configureTestingModule({
                providers: [ SIGNALR_PROVIDER, SignalrService ]
            });
        })
    );

    it('should exist', () => {
        const connectionService = TestBed.get(SignalrService);
        expect(connectionService).toBeTruthy();
    });
});

In my second attempt, I assigned the mocked object to a variable:

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

import { SignalrService } from './signalr-service';

const SIGNALR_TOKEN = new InjectionToken('signalR');

const SIGNALR_VALUE = {
    HubConnectionBuilder: () => {
        return {
            withUrl: (url) => {
                return {
                    build: () => {}
                };
            }
        };
    }
};

const SIGNALR_PROVIDER = [ { provide: SIGNALR_TOKEN, useValue: SIGNALR_VALUE } ];

describe('ConnectionService', () => {
    beforeEach(
        async(() => {
            TestBed.configureTestingModule({
                providers: [ SIGNALR_PROVIDER, SignalrService ]
            });
        })
    );

    it('should exist', () => {
        const connectionService = TestBed.get(SignalrService);
        expect(connectionService).toBeTruthy();
    });
});

In my third attempt, I took out the SIGNALR_PROVIDER and tried to provide the value directly in the providers array by factory and by direct value:

Direct value:

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

import { SignalrService } from './signalr-service';

const SIGNALR_TOKEN = new InjectionToken('signalR');

const SIGNALR_VALUE = {
    HubConnectionBuilder: () => {
        return {
            withUrl: (url) => {
                return {
                    build: () => {}
                };
            }
        };
    }
};

describe('ConnectionService', () => {
    beforeEach(
        async(() => {
            TestBed.configureTestingModule({
                providers: [ { provide: SIGNALR_TOKEN, useValue: SIGNALR_VALUE }, SignalrService ]
            });
        })
    );

    it('should exist', () => {
        const connectionService = TestBed.get(SignalrService);
        expect(connectionService).toBeTruthy();
    });
});

Factory:

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

import { SignalrService } from './signalr-service';

const SIGNALR_TOKEN = new InjectionToken('signalR');

function signalRFactory() {
    return {
        HubConnectionBuilder: () => {
            return {
                withUrl: (url) => {
                    return {
                        build: () => {}
                    };
                }
            };
        }
    };
}

describe('ConnectionService', () => {
    beforeEach(
        async(() => {
            TestBed.configureTestingModule({
                providers: [ { provide: SIGNALR_TOKEN, useFactory: signalRFactory }, SignalrService ]
            });
        })
    );

    it('should exist', () => {
        const connectionService = TestBed.get(SignalrService);
        expect(connectionService).toBeTruthy();
    });
});

Each attempt still gives me NullInjectorError: No provider for InjectionToken signalR! I'm stuck. There's obviously something I don't know about the InjectionToken. Could anyone point me in the right direction?


回答1:


Figured it out--my problem was that I redefined the token in the test file, so my test was providing my mocked object for a completely different token than the one my service was expecting. Changing it to this fixed it:

// signalr-service.spec.ts

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

import { SignalrService } from './signalr-service';

import { SIGNALR_TOKEN } from './signalr-provider'; // This is the change

function signalRFactory() {
    return {
        HubConnectionBuilder: () => {
            return {
                withUrl: (url) => {
                    return {
                        build: () => {}
                    };
                }
            };
        }
    };
}

const SIGNALR_PROVIDER = [ { provide: SIGNALR_TOKEN, useFactory: signalRFactory } ];

describe('ConnectionService', () => {
    beforeEach(
        async(() => {
            TestBed.configureTestingModule({
                providers: [ SIGNALR_PROVIDER, SignalrService ]
            });
        })
    );

    it('should exist', () => {
        const connectionService = TestBed.get(SignalrService);
        expect(connectionService).toBeTruthy();
    });
});


来源:https://stackoverflow.com/questions/52607342/angular-testing-mocking-an-injectiontoken

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!