Angular2 / Jasmine:使用另一个服务作为依赖项测试服务

时间:2017-09-12 10:18:12

标签: angular unit-testing dependency-injection jasmine

设置:APP_INITIALIZER服务ConfigurationService,用于加载配置数据,用于跨应用程序组件和服务。单元测试组件时,它会读取配置文件并正确评估。当使用配置的单元测试服务失败时。

设置如下:

app.module.ts

export function ConfigurationFactory( config: ConfigurationService ): any {
  // this line actually triggers the loading of the config file  
  return () => config.load(); 
}

@NgModule({
  bootrstrap: {},
  ...,
  providers : [
    ConfigurationService,
    {
      deps: [ ConfigurationService ],
      multi: true,
      provide: APP_INITIALIZER,
      useFactory: ConfigurationFactory,   
    }
  ]
})

ConfigurationService.ts

@Injectable()
export class ConfigurationService {

  // error holder
  public error: boolean = false;
  // data holder
  public data: any = {};
  // config file url
  private configUrl: string = './assets/config/config.json';

  constructor( private http: Http ) {
  }
  /**
   * reads config file
   */
  public load(): Promise<any> {
    return this.http.get( this.configUrl )
      .map( ( response: any ) => response.json() )
      .toPromise()
      .then( ( data: any ) => this.data = data )
      .catch( ( err: any ) => Promise.resolve() );
  }

}

以下是使用配置服务的 TestableService.service.ts

@Injectable()
export class TestableService extends SomeAbstractHttpService {
  constructor( http: Http,
               private config: ConfigurationService ) {
    super( http );
  }

  public setRequest( sessionId: string, isIdent: boolean ): void {
    if ( isIdent ) {
      this.resourceUrl = `${ this.config.data.contextRootRest}/documents/ident/${sessionId}`;
    } else {
      this.resourceUrl = `${ this.config.data.contextRootRest}/documents/noident/${sessionId}`;
    }
    this.headers = new Headers();
    this.headers.append( 'Content-Type', 'application/json' );
    this.headers.append( 'Accept', 'application/json, application/pdf' );
    this.requestOptions = new RequestOptions( {
      headers: this.headers,
      responseType: ResponseContentType.Blob
    } );
  }
}

现在到spec文件, TestableService.service.spec.ts

export function ConfigurationFactory( config: ConfigurationService ): any {
  return () => config.load();
}

describe( 'TestableService', () => {

  // not ideal to hardcode it here, I would prefer using the same config file, which is used across the app!
  let mockConfig: any = {contextRootRest: 'rest/'};
  let theService: TestableService; 

  beforeEach( async(() => {
    TestBed.configureTestingModule( {
      imports: [
        HttpModule,
      ],
      providers: [
        TestableService,
        MockDataService,
        { provide: XHRBackend, useClass: MockBackend },
        ConfigurationService,
        {
          // Provider for APP_INITIALIZER
          deps: [ ConfigurationService ],
          multi: true,
          provide: APP_INITIALIZER,
          useFactory: ConfigurationFactory,
        },
      ]
    } );
  } ) );

  // I suspect, that using APP_INITIALIZER at this stage is goofy, 
  // as I do not in fact instantiate the app, only the service. 
  // What would be the correct way to do it here?

  beforeEach( inject( [ TestableService, XHRBackend ], ( service: TestableService, backend: MockBackend ) => {
    theService = service;
    mockBackend = backend;
  } ) );  

  it( 'should set request correctly', ( ) => {
    theService.setRequest( '1234567890', true );
    expect( theService.resourceUrl )
        .toEqual( `${ mockConfig.contextRootRest }/documents/ident/1234567890` );
  });
});

它失败了Expected 'undefined/documents/ident/1234567890' to equal 'rest/documents/ident/1234567890' - 这意味着Config服务根本没有加载到TestableService。有什么想法吗?

1 个答案:

答案 0 :(得分:0)

上面的代码实际上很糟糕。我们想出了几个小时后的方式,这里是一个更清洁,更好的解决方案。

因此,由于配置文件位于我们的资产目录中,我们实际上可以将其直接导入到spec文件中。我们还为ConfigurationService使用了一个模拟的提供程序。这是 spec.ts 文件

import { ConfigurationService } from '../../common/config/configuration.service';
import * as config from '../../../assets/config/config.json';

const mockConfig: any = config;

export class ConfigurationFactory {
  // no methods as in the original ConfigurationService, just the object var
  public data: any = mockConfig;
}

describe( 'TestableService', () => {

  let theService: TestableService;

  beforeEach( () => {
    TestBed.configureTestingModule( {
      providers: [
        TestableService, ...
        { provide: ConfigurationService, useClass: ConfigurationFactory },
      ]
    } );
  } );

  beforeEach( inject( [ TestableService ],
      ( service: TestableService ) => {
        theService = service;
  } ) );

  it( 'should set request correctly', ( ) => {
    theService.setRequest( '1234567890', true );
    expect( theService.resourceUrl )
        .toEqual( `${ mockConfig.contextRootRest }/documents/ident/1234567890` );
  });
}

另一个以太复杂的方式解决的事情的例子,表面上更简单的解决方案......:/