0
\$\begingroup\$

I want to write the test case for Service class.

My Service Class dynamic-form.service.ts

import { HttpClient, HttpBackend } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';

@Injectable({
  providedIn: 'root'
})
export class DynamicFormService {

  private http: HttpClient;

  constructor(private handler: HttpBackend
  ) {
    this.http = new HttpClient(handler);
  }

  async getDynamicFormData(paramName: any, data?: any): Promise<any> {
    return await this.http.get(this.HostUrl + paramName).toPromise();
  }

  async getTaskByWorkflowId(paramNameWithId): Promise<any> {
    return await this.http.get(this.HostUrl + paramNameWithId).toPromise();
  }

  async getAccontType(paramNameWithId): Promise<any> {
    return await this.http.get(this.HostUrl + paramNameWithId).toPromise();
  }
}

My dynamic-form.spec.ts are as follows

import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { TestBed} from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { of } from 'rxjs';

import { DynamicFormServiceService } from './dynamic-form-service.service';

describe('DynamicFormServiceService', () => {
  let service: DynamicFormServiceService;
  let httpMock: HttpTestingController;
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule, RouterTestingModule.withRoutes([])]
    });
    service = TestBed.inject(DynamicFormServiceService);
    httpMock= TestBed.inject(HttpTestingController);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });
});

I have written one of service method test case as follows

it('should call getAccontType', ()=>{
    const data = [
      {
        "id": 4,
        "name": "24hrs"
      },
      {
        "id": 5,
        "name": "48hrs"
      }];
    service.getAccontType(`account-type`).then(res=> expect(res.length).toBeGreaterThan(0));
    const request = httpMock.expectOne(`http://localhost:8080/account-type`);
    expect(request.request.method).toBe('GET');
    request.flush(data);
    httpMock.verify();
  });

I want to know it is good way to write test-case this way or I can do better. Any suggestion.

\$\endgroup\$
3
  • \$\begingroup\$ Welcome to the Code Review Community. What we do here is review working code from one of your projects and make suggestions about how to improve the code. I want to know it is good way to write test-case this way or I can do better. is difficult to answer. Before you post a question here, the code should be working and getting the expected answers. \$\endgroup\$
    – pacmaninbw
    Apr 20, 2021 at 15:29
  • \$\begingroup\$ To add: codereview.stackexchange.com/help/on-topic \$\endgroup\$
    – am2021
    Apr 20, 2021 at 19:17
  • \$\begingroup\$ My Test case is running fine. I want my code-coverage to be good. \$\endgroup\$ Apr 21, 2021 at 4:00

1 Answer 1

0
\$\begingroup\$

Personally i would create an interface for the expected data from the backend. any is something i try to avoid, because then my IDE can´t help me finding my stupid mistakes.

For example

interface DynamicFormData {
   aValue: string,
   anotherValue: number,
}

async getDynamicFormData(paramName: string, data?: SomeDataType): Promise<DynamicFormData > {
  return await this.http.get(this.HostUrl + paramName).toPromise();
}

It gives you the benefit, that whereever you call that method, the IDE will know how the data will look like and what methods could be performed on it.

Also personally i like working with RxJS more than Promises, but thats a personal habit. But it allows to filter, modify, do stuff on the response. And i stay away from "async/await", because i do not like blocking tasks in my frontend if i can avoid them. But as i said, personal style.

About the init of the testcases, why do you need to import the RouterTestingModule? Its not used in the service at all.

About the test coverage:
When i see a method interface like getDynamicFormData(paramName: any, data?: any) then i would ask myself if "paramName" is mandatory or not. Also test cases are a kind of documentation, therefore i would add a testcase with an empty paramName (for example null).

From a Unit-testing perspective i would avoid hard coded strings like "account-type". You will surely use this strings also at other places in the regular code when you want to call the service. If those strings change, you have to find all places where they are used and replace them. Therefore i would create constants for those and us those constants. Then if the API changes ( e.g. "account-type" changes to "type-of-account") you just have to change it at one place and all (code and unit test) will adept automaticly.

You specificly asked in the comment for "code-coverage".
For that we have to think about what could be the possible results of each method. The Promise could be resolved or fail. Quite easy.

BUT your service is instantiated with a parameter

constructor(private handler: HttpBackend) {
    this.http = new HttpClient(handler);
 }

If you do not provide any input (as you do in your unit test), then its instantiated with this.http = new HttpClient();. I always use the HTTPClient provided directly by Angular, therefor i do not really know the effect of this. From the documentation it seems that this would circumvent the interceptors. But be it as it be, as its an input that may alter the behavior of your service, its something that should be covered by your testcases.

Hope you could use some of the mentioned things. :-)

warm regards Jan

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.