Asif Zaidi
Asif Zaidi

Reputation: 93

How to spy on methods of service when the service is provided in the root of Component

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:

  1. As I have used providers in the Profile Component, can I inject the ProfileService in the profile.component.spec.ts file as it will create two instance of the same service?
  2. How to spy on login() and getData() of ProfileService as below statement is not working...?
spyOn(profileService, 'login').and.returnValue({});

Please help me on this.

Upvotes: 4

Views: 3546

Answers (3)

Shashank Vivek
Shashank Vivek

Reputation: 17514

  1. As I have used providers in the Profile Component, can I inject the Profileervice in the profile.component.spec.ts file as it will create two instance of the same service..??

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();
  1. How to spy on login() and getData() of ProfileService as below statement is not working...?

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

Jasdeep Singh
Jasdeep Singh

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

AliF50
AliF50

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

Related Questions