Reputation: 93
I am writing unit test for a angular component using karma-jasmine. I have used providers in my ProfileComponent and injected ProfileService.
profile.component.ts
@Component({
selector: 'app-profile',
templateUrl: './profile.component.html',
styleUrls: ['./profile.component.scss'],
providers: [ProfileService]
})
export class ProfileComponent implements OnInit {
public data;
constructor(private profileService: ProfileService) {}
ngOnInit() {
await this.profileService.login();
this.profileService.getData('A').subscribe((result) => {
this.data = result;
});
}
}
profile.service.ts
@Injectable()
export class ProfileService {
private baseUrl: string;
constructor(private http: HttpClient) {
this.baseUrl = 'https://localhost:4002';
}
public async login() {
const loginUrl = `${this.baseUrl}/api/Login`;
return this.http.post(loginUrl, {}, { headers }).toPromise();
}
getData(id: string) {
const url = `${this.baseUrl}/api/GetData/${id}`;
return this.http.get(url, { headers });
}
}
profile.component.spec.ts
const data = ['A', 'B', 'C'];
describe('Client.App.ProfileComponent', () => {
let component: ProfileComponent;
let fixture: ComponentFixture < ProfileComponent > ;
let profileService: ProfileService;
let profileData;
beforeEach(async (() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule, ReactiveFormsModule, RouterTestingModule],
declarations: [ProfileComponent],
providers: [ProfileService],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
}));
beforeEach(fakeAsync(() => {
profileData = new Subject();
profileService = TestBed.inject < ProfileService > (ProfileService);
spyOn(profileService, 'login').and.returnValue({});
spyOn(profileService, 'getData').and.returnValue(profileData);
fixture = TestBed.createComponent(ProfileComponent);
component = fixture.componentInstance;
fixture.detectChanges();
}));
it('shall create the component', () => {
profileData.next(data);
fixture.detectChanges();
expect(component).toBeDefined();
});
});
I added a debugger and checked that its failing in login. So, I have two questions:
Profile Component
, can I inject the ProfileService
in the profile.component.spec.ts
file as it will create two instance of the same service?spyOn(profileService, 'login').and.returnValue({});
Please help me on this.
Upvotes: 4
Views: 3546
Reputation: 17514
Answer: You should create a Stub
for the service to replace dependency on actual ProfileService
using useClass
export class ProfileServiceStub {
getData(id: string) {
return of({some_obj: "value"})
}
}
and in spec.ts
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule, ReactiveFormsModule, RouterTestingModule],
declarations: [ProfileComponent],
providers: [ {provide: ProfileService , useClass: ProfileServiceStub } ], // <-- useclass here
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
Answer: Make the service public
in component constructor as below:
constructor(public profileService: ProfileService) { }
I would strongly recommend you to read my article on something similar where I have tested the component
Upvotes: 4
Reputation: 8321
First of all you need to provide a mock instead of providing the actual service instance. After that you need to provide a fake implementation of that service method. Please find below the required changes and inline comments.
const data =['A','B','C'];
describe('Client.App.ProfileComponent', () => {
let component: ProfileComponent;
let fixture: ComponentFixture<ProfileComponent>;
let profileService: jasmine.SpyObj<ProfileService>;
beforeEach(async(() => {
// create a spied service and use it inside providers array
profileService = jasmine.createSpyObj('ProfileService', ['login', 'getData']);
TestBed.configureTestingModule({
imports: [HttpClientTestingModule, ReactiveFormsModule, RouterTestingModule],
declarations: [ProfileComponent],
providers: [ { provide: ProfileService, useValue: profileService ],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
// provide fake implementation to service methods
profileService.login.and.returnValue(Promise.resolve('success'));
profileService.getData.and.returnValue(of(data)); // of is rxjs operators- import
fixture = TestBed.createComponent(ProfileComponent);
component = fixture.componentInstance;
fixture.detectChanges();
}));
it('shall create the component', () => {
expect(component).toBeDefined();
});
});
Upvotes: 0
Reputation: 18859
1.) I think what you have is fine, putting ProfileService
in the provider
array in TestBed.configureTestingModule
is good and when the component gets created, it knows how to make it. Each time the component gets created, it will be a separate provider but this will be the case whether providing the provider in the component or globally since a test constructs and destructs (beforeEach.... TestBed.configureTestingModule....).
2.) Try `spyOn(profileService, 'login').and.returnValue(Promise.resolve({}));
Side note: The ngOnInit
needs an async decorator in order to use await.
Upvotes: 0