Currently, I am in the process of developing a small toy application in Angular that consists of various classes, including ProductDetailComponent
and ProductService
. The ProductService class contains a method responsible for making an HTTP GET request for a specific product, while the ProductDetailComponent class is responsible for displaying the details of a product and utilizes the ProductService to retrieve the product information. However, upon adding the HttpModule
to the application (as well as to the service within it), I encountered failures in my component tests, with an error message stating "Error: No provider for Http!".
Interestingly, when I imported the HttpModule
, the tests passed successfully. Nonetheless, I find myself perplexed as to why this dependency is required in the first place, given that it is the productService's dependency, which I have already mocked out using a provider.
In summary, my question revolves around why the tests are indicating the necessity of this particular dependency when the class being tested does not utilize it?
product-detail.component.ts
import {Component, OnInit} from '@angular/core';
import {ActivatedRoute, Params} from "@angular/router";
import {ProductService} from "../product.service";
import {Product} from "../product";
import 'rxjs/add/operator/switchMap';
@Component({
selector: 'app-product-detail',
templateUrl: './product-detail.component.html',
styleUrls: ['./product-detail.component.css'],
providers: [ProductService]
})
export class ProductDetailComponent implements OnInit {
product: Product;
constructor(
private productService: ProductService,
private route: ActivatedRoute,
) { }
ngOnInit() {
this.route.params
.switchMap((params: Params) => this.productService.getProduct(+params['id']))
.subscribe(product => this.product = product);
}
}
product.service.ts
import {Injectable} from "@angular/core";
import {Product} from "./product";
import {Http} from "@angular/http";
import "rxjs/add/operator/toPromise";
@Injectable()
export class ProductService {
private products: Product[];
constructor(private http: Http) { }
getProducts(): Promise<Product[]> {
return this.http
.get('/api/products/')
.toPromise()
.then(response => response.json() as Product[])
.catch(error => {
console.error('An error occurred', error);
return Promise.reject(error.message || error)
});
}
getProduct(id: number): Promise<Product> {
return this.http
.get(`/api/products/${id}`)
.toPromise()
.then(response => response.json() as Product)
.catch(error => {
console.error('An error occurred', error);
return Promise.reject(error.message || error)
});
}
}
product-detail.component.spec.ts:
import {async, ComponentFixture, TestBed} from "@angular/core/testing";
import {ProductDetailComponent} from "./product-detail.component";
import {ActivatedRoute} from "@angular/router";
import {Observable} from "rxjs";
import {ProductService} from "../product.service";
import {Product} from "../product";
import {By} from "@angular/platform-browser";
import {DebugElement} from "@angular/core";
import {HttpModule} from "@angular/http";
describe('ProductDetailComponent', () => {
let component: ProductDetailComponent;
let fixture: ComponentFixture<ProductDetailComponent>;
let debugElement: DebugElement;
let element: HTMLElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ProductDetailComponent],
//imports: [HttpModule], //un-commenting this fixes the breakages
providers: [{
provide: ActivatedRoute,
useValue: {params: Observable.from([{'id': 1}])},
}, {
provide: ProductService,
useValue: {
getProduct: (id: number) => Promise.resolve(new Product(id, 'Example Product Name', 20))
}
}]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ProductDetailComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should be stable', () => {
fixture.whenStable().then(() => {
expect(fixture.isStable()).toBe(true);
});
});
it('should display the title', () => {
fixture.whenStable().then(() => {
debugElement = fixture.debugElement.query(By.css('.name'));
element = debugElement.nativeElement;
expect(element.textContent).toEqual('Example Product Name')
});
});
it('should display the price', () => {
fixture.whenStable().then(() => {
debugElement = fixture.debugElement.query(By.css('.span'));
element = debugElement.nativeElement;
expect(element.textContent).toEqual('$ 20.0')
});
});
});