如何正确地将 HttpClient 注入到依赖项中?
How to correctly inject HttpClient into a dependency?
我开始学习测试 Angular 服务。 AngularTesting Guide里面有官方的例子。但在示例中,一个服务对另一个服务的依赖的更简单版本。
/// HeroService method tests begin ///
describe('#getHeroes', () => {
let expectedHeroes: Hero[];
beforeEach(() => {
heroService = TestBed.inject(HeroService);
expectedHeroes = [
{ id: 1, name: 'A' },
{ id: 2, name: 'B' },
] as Hero[];
});
it('should return expected heroes (called once)', () => {
heroService.getHeroes().subscribe(
heroes => expect(heroes).toEqual(expectedHeroes, 'should return expected heroes'),
fail
);
const req = httpTestingController.expectOne(heroService.heroesUrl);
expect(req.request.method).toEqual('GET');
req.flush(expectedHeroes);
});
我有一个 TerritoryApiService,其中包含一组用于处理区域列表的方法。
@Injectable({
providedIn: 'root'
})
export class TerritoryApiService {
private nameApi: string = 'territory';
constructor(private apiclient : ApiclientService) {}
public GetStaffTerritories(dateFrom: Date, dateTo: Date) {
let parameters = new Map();
parameters.set('dateFrom', dateFrom.toISOString());
parameters.set('dateTo', dateTo.toISOString());
return this.apiclient.doPostT<StaffTerritory[]>(this.nameApi, 'GetStaffTerritories', parameters);
}
}
TerritoryApiService依赖于ApiClientService,包含URL的HttpClient和AppConfig传递给ApiClientService
@Injectable({
providedIn: 'root'
})
export class ApiclientService {
private apiurl: string;
constructor(private http: HttpClient, @Inject(APP_CONFIG) config: AppConfig) {
this.apiurl = config.apiEndpoint;
}
public doPostT<T> (url: string, method :string, parameters: Map<string,string> ) {
let headers = new HttpHeaders();
let httpParams = new HttpParams();
if (parameters != undefined) {
for (let [key, value] of parameters) {
httpParams = httpParams.append(key, value);
}
}
return this.http.post<T>(this.apiurl +'/v1/' + url + '/' + method, httpParams, {
headers: headers,
params: httpParams
})
}
}
const appConfig: AppConfig = {
apiEndpoint: environment.apiEndpoint
};
export const environment = {
production: false,
apiEndpoint: 'https://localhost:44390/api'
};
请告诉我如何正确准备测试(配置所有依赖项)?因为现在我有两种情况:
1.If 我只是在 provide 部分指定了 ApiclientService,然后测试通过但出现错误,因为 appConfig 未定义(URL 变为 'undefined/v1/territory/GetStaffTerritories')
TestBed.configureTestingModule({
imports: [ HttpClientTestingModule ],
providers: [
TerritoryApiService,
ApiclientService,
{ provide: APP_CONFIG, useValue: APP_CONFIG }
]
});
如果我指定使用什么作为 ApiclientService,那么我需要显式创建 HttpClient 并将其传递给构造函数。在这种情况下,测试中出现错误post方法未定义。那么需要创建HttpClient吗?
const appConfig: AppConfig = {
api端点:environment.apiEndpoint
};
TestBed.configureTestingModule({
进口:[ HttpClientTestingModule ],
供应商:[
领土Api服务,
{ provide: ApiclientService, useValue: new ApiclientService(httpClient, appConfig) },
{ 提供:APP_CONFIG,使用价值:APP_CONFIG }
]});
完整的测试代码
describe('TerritoryApiService', () => {
let service: TerritoryApiService;
let httpClient: HttpClient;
let httpTestingController: HttpTestingController;
const staffTerritoriesStub: StaffTerritory[] = [{
id: 1,
name: 'StaffTerritory',
regionCode: 29,
createDate: new Date(),
creatorId: 0,
dateFrom: new Date(),
dateTo: null,
dateToSelect: new Date(),
dateFromChanger: 0,
dateToChanger: null,
}];
const appConfig: AppConfig = {
apiEndpoint: environment.apiEndpoint
};
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ HttpClientTestingModule ],
providers: [
TerritoryApiService,
{ provide: ApiclientService, useValue: new ApiclientService(httpClient, appConfig) },
{ provide: APP_CONFIG, useValue: APP_CONFIG }
]
});
httpClient = TestBed.inject(HttpClient);
httpTestingController = TestBed.inject(HttpTestingController);
service = TestBed.inject(TerritoryApiService);
});
afterEach(() => {
httpTestingController.verify();
});
describe('#GetStaffTerritories', () => {
beforeEach(() => {
service = TestBed.inject(TerritoryApiService);
});
it('should return expected heroes (called once)', () => {
service.GetStaffTerritories(new Date(), new Date()).subscribe(
staffTerritories => expect(staffTerritories).toEqual(staffTerritoriesStub, 'should return expected staffTerritories'),
fail
);
const req = httpTestingController.expectOne(appConfig.apiEndpoint + '/v1/' + 'territory' + '/' + 'GetStaffTerritories');
req.flush(staffTerritoriesStub);
});
});
});
我认为你提供的 AppConfig
是错误的,在你的情况下,我不会用 new ApiclientService(httpClient, appConfig)
来模拟 ApiclientService
因为你给出了 HttpClient
的实际实现HttpClientTestingModule
.
试试这个:
describe('TerritoryApiService', () => {
let service: TerritoryApiService;
let httpClient: HttpClient;
let httpTestingController: HttpTestingController;
const staffTerritoriesStub: StaffTerritory[] = [{
id: 1,
name: 'StaffTerritory',
regionCode: 29,
createDate: new Date(),
creatorId: 0,
dateFrom: new Date(),
dateTo: null,
dateToSelect: new Date(),
dateFromChanger: 0,
dateToChanger: null,
}];
const appConfig: AppConfig = {
apiEndpoint: environment.apiEndpoint // make sure environment.apiEndpoint is defined
};
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ HttpClientTestingModule ],
providers: [
TerritoryApiService,
ApiclientService, // provide the actual ApiclientService since you have the dependencies
{ provide: AppConfig, useValue: appConfig } // this line needs to change to this
]
});
httpClient = TestBed.inject(HttpClient);
httpTestingController = TestBed.inject(HttpTestingController);
service = TestBed.inject(TerritoryApiService);
});
afterEach(() => {
httpTestingController.verify();
});
describe('#GetStaffTerritories', () => {
beforeEach(() => {
service = TestBed.inject(TerritoryApiService);
});
it('should return expected heroes (called once)', (done) => { // add done here to have a handle to let Jasmine know when we are done with our assertions
service.GetStaffTerritories(new Date(), new Date()).subscribe(
staffTerritories => {
expect(staffTerritories).toEqual(staffTerritoriesStub, 'should return expected
staffTerritories');
done(); // I would put a done here to ensure that the subscribe block was traversed
},
fail
);
const req = httpTestingController.expectOne(appConfig.apiEndpoint + '/v1/' + 'territory' + '/' + 'GetStaffTerritories');
req.flush(staffTerritoriesStub);
});
});
});
我开始学习测试 Angular 服务。 AngularTesting Guide里面有官方的例子。但在示例中,一个服务对另一个服务的依赖的更简单版本。
/// HeroService method tests begin ///
describe('#getHeroes', () => {
let expectedHeroes: Hero[];
beforeEach(() => {
heroService = TestBed.inject(HeroService);
expectedHeroes = [
{ id: 1, name: 'A' },
{ id: 2, name: 'B' },
] as Hero[];
});
it('should return expected heroes (called once)', () => {
heroService.getHeroes().subscribe(
heroes => expect(heroes).toEqual(expectedHeroes, 'should return expected heroes'),
fail
);
const req = httpTestingController.expectOne(heroService.heroesUrl);
expect(req.request.method).toEqual('GET');
req.flush(expectedHeroes);
});
我有一个 TerritoryApiService,其中包含一组用于处理区域列表的方法。
@Injectable({
providedIn: 'root'
})
export class TerritoryApiService {
private nameApi: string = 'territory';
constructor(private apiclient : ApiclientService) {}
public GetStaffTerritories(dateFrom: Date, dateTo: Date) {
let parameters = new Map();
parameters.set('dateFrom', dateFrom.toISOString());
parameters.set('dateTo', dateTo.toISOString());
return this.apiclient.doPostT<StaffTerritory[]>(this.nameApi, 'GetStaffTerritories', parameters);
}
}
TerritoryApiService依赖于ApiClientService,包含URL的HttpClient和AppConfig传递给ApiClientService
@Injectable({
providedIn: 'root'
})
export class ApiclientService {
private apiurl: string;
constructor(private http: HttpClient, @Inject(APP_CONFIG) config: AppConfig) {
this.apiurl = config.apiEndpoint;
}
public doPostT<T> (url: string, method :string, parameters: Map<string,string> ) {
let headers = new HttpHeaders();
let httpParams = new HttpParams();
if (parameters != undefined) {
for (let [key, value] of parameters) {
httpParams = httpParams.append(key, value);
}
}
return this.http.post<T>(this.apiurl +'/v1/' + url + '/' + method, httpParams, {
headers: headers,
params: httpParams
})
}
}
const appConfig: AppConfig = {
apiEndpoint: environment.apiEndpoint
};
export const environment = {
production: false,
apiEndpoint: 'https://localhost:44390/api'
};
请告诉我如何正确准备测试(配置所有依赖项)?因为现在我有两种情况:
1.If 我只是在 provide 部分指定了 ApiclientService,然后测试通过但出现错误,因为 appConfig 未定义(URL 变为 'undefined/v1/territory/GetStaffTerritories')
TestBed.configureTestingModule({
imports: [ HttpClientTestingModule ],
providers: [
TerritoryApiService,
ApiclientService,
{ provide: APP_CONFIG, useValue: APP_CONFIG }
]
});
如果我指定使用什么作为 ApiclientService,那么我需要显式创建 HttpClient 并将其传递给构造函数。在这种情况下,测试中出现错误post方法未定义。那么需要创建HttpClient吗?
const appConfig: AppConfig = { api端点:environment.apiEndpoint }; TestBed.configureTestingModule({ 进口:[ HttpClientTestingModule ], 供应商:[ 领土Api服务, { provide: ApiclientService, useValue: new ApiclientService(httpClient, appConfig) }, { 提供:APP_CONFIG,使用价值:APP_CONFIG } ]});
完整的测试代码
describe('TerritoryApiService', () => {
let service: TerritoryApiService;
let httpClient: HttpClient;
let httpTestingController: HttpTestingController;
const staffTerritoriesStub: StaffTerritory[] = [{
id: 1,
name: 'StaffTerritory',
regionCode: 29,
createDate: new Date(),
creatorId: 0,
dateFrom: new Date(),
dateTo: null,
dateToSelect: new Date(),
dateFromChanger: 0,
dateToChanger: null,
}];
const appConfig: AppConfig = {
apiEndpoint: environment.apiEndpoint
};
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ HttpClientTestingModule ],
providers: [
TerritoryApiService,
{ provide: ApiclientService, useValue: new ApiclientService(httpClient, appConfig) },
{ provide: APP_CONFIG, useValue: APP_CONFIG }
]
});
httpClient = TestBed.inject(HttpClient);
httpTestingController = TestBed.inject(HttpTestingController);
service = TestBed.inject(TerritoryApiService);
});
afterEach(() => {
httpTestingController.verify();
});
describe('#GetStaffTerritories', () => {
beforeEach(() => {
service = TestBed.inject(TerritoryApiService);
});
it('should return expected heroes (called once)', () => {
service.GetStaffTerritories(new Date(), new Date()).subscribe(
staffTerritories => expect(staffTerritories).toEqual(staffTerritoriesStub, 'should return expected staffTerritories'),
fail
);
const req = httpTestingController.expectOne(appConfig.apiEndpoint + '/v1/' + 'territory' + '/' + 'GetStaffTerritories');
req.flush(staffTerritoriesStub);
});
});
});
我认为你提供的 AppConfig
是错误的,在你的情况下,我不会用 new ApiclientService(httpClient, appConfig)
来模拟 ApiclientService
因为你给出了 HttpClient
的实际实现HttpClientTestingModule
.
试试这个:
describe('TerritoryApiService', () => {
let service: TerritoryApiService;
let httpClient: HttpClient;
let httpTestingController: HttpTestingController;
const staffTerritoriesStub: StaffTerritory[] = [{
id: 1,
name: 'StaffTerritory',
regionCode: 29,
createDate: new Date(),
creatorId: 0,
dateFrom: new Date(),
dateTo: null,
dateToSelect: new Date(),
dateFromChanger: 0,
dateToChanger: null,
}];
const appConfig: AppConfig = {
apiEndpoint: environment.apiEndpoint // make sure environment.apiEndpoint is defined
};
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ HttpClientTestingModule ],
providers: [
TerritoryApiService,
ApiclientService, // provide the actual ApiclientService since you have the dependencies
{ provide: AppConfig, useValue: appConfig } // this line needs to change to this
]
});
httpClient = TestBed.inject(HttpClient);
httpTestingController = TestBed.inject(HttpTestingController);
service = TestBed.inject(TerritoryApiService);
});
afterEach(() => {
httpTestingController.verify();
});
describe('#GetStaffTerritories', () => {
beforeEach(() => {
service = TestBed.inject(TerritoryApiService);
});
it('should return expected heroes (called once)', (done) => { // add done here to have a handle to let Jasmine know when we are done with our assertions
service.GetStaffTerritories(new Date(), new Date()).subscribe(
staffTerritories => {
expect(staffTerritories).toEqual(staffTerritoriesStub, 'should return expected
staffTerritories');
done(); // I would put a done here to ensure that the subscribe block was traversed
},
fail
);
const req = httpTestingController.expectOne(appConfig.apiEndpoint + '/v1/' + 'territory' + '/' + 'GetStaffTerritories');
req.flush(staffTerritoriesStub);
});
});
});