2

I started working on testing components and service in Angular. I watched a course on pluralsight and tried to follow ideas from: https://codecraft.tv/courses/angular/unit-testing/mocks-and-spies/ however, I have a problem with the testing component method. Unfortunately, I cannot find a solution so decided to ask you for help.

My service:

@Injectable()
export class MyService {
  private config: AppConfig;
  constructor(private apiService: ApiService, private configService: ConfigurationService) {
    this.config = configService.instant<AppConfig>();
  }

  public get(name: string, take: number = 10, skip: number = 0, params?:any): Observable<any> {
    return this.apiService.post(`${this.config.baseUrl}/${name}/paginated?take=${take}&skip=${skip}`, params);
  }
}

My Component:

 @Component({
  selector: 'my',
  templateUrl: './my.component.html',
  styleUrls: ['./my.component.scss']
})
export class MyComponent implements OnInit {
  @Input("customerId") customerId: string;
  items: CustomerItem[] = [];

  public pagingInfo: PagingMetadata = {
    itemsPerPage: 5,
    currentPage: 1,
    totalItems: 0
  };
  constructor(private service: MyService) { }

  ngOnInit() {
    if (this.customerId) {
      this.updateItems();
    }
  }

  updateItems() {
    let skip = (this.pagingInfo.currentPage - 1) * this.pagingInfo.itemsPerPage;
    let take = this.pagingInfo.itemsPerPage;
    this.service.get("customer", take, skip, { customerId: this.customerId }).subscribe(result => {
      this.items = result.entities;
      this.pagingInfo.totalItems = result.total;
    }, (error) => {
      console.log(error.message);
    });
  }
}

My my.component.spec.ts test file:

describe('MyComponent', () => {
  let component: MyComponent;
  let fixture: ComponentFixture<MyComponent>;
  let mockService;
  let ITEMS = [
    {
        "title": "test",
        "id": "5e188d4f-5678-461b-8095-5dcffec0855a"
    },
    {
        "title": "test2",
        "id": "5e188d4f-1234-461b-8095-5dcffec0855a"
    }
]

beforeEach(async(() => {
  mockService = jasmine.createSpyObj(['get']);

  TestBed.configureTestingModule({
    imports: [NgxPaginationModule, RouterTestingModule],
    declarations: [MyComponent],
    providers: [
      { provide: MyService, useValue: mockService }
    ]
  })
    .compileComponents();
}));

beforeEach(() => {
  fixture = TestBed.createComponent(MyComponent);
  component = fixture.componentInstance;
  fixture.detectChanges();
});

// works fine
it('should create', () => {
  expect(component).toBeTruthy();
});

// works fine
it('should NOT call updateItems method on initialization', () => {
  component.ngOnInit();
  let spy = spyOn(component, 'updateItems').and.callThrough();

  expect(spy).not.toHaveBeenCalled();
});

// works fine
it('should call updateItems method on initialization', () => {
    component.customerId = "1";
    let spy = spyOn(component, 'updateItems').and.callFake(() => { return null });

    component.ngOnInit();

    expect(spy).toHaveBeenCalled();
  });

// gives error
it('should update items', () => {
  component.pagingInfo.currentPage = 1;
  component.pagingInfo.itemsPerPage = 10;
  component.customerId = "1";
  mockService.get.and.returnValue(of(ITEMS));

  component.updateItems();

  expect(component.items).toBe(ITEMS);
});
});

3 first tests work fine, however for the last - updating items I got error:

Expected undefined to be [ Object({"title": "test","id": "5e188d4f-5678-461b-8095-5dcffec0855a"},{"title": "test2","id": "5e188d4f-1234-461b-8095-5dcffec0855a"})]

I would be very grateful for any tips ;)

korn
  • 65
  • 1
  • 6

1 Answers1

3

Very complete question, thank you! It allowed me to put it all up in a StackBlitz to be sure I found out the issue you were facing correctly. :)

In that StackBlitz you can see the tests are now all passing. I made only a single change to what you had in order to get them to pass, I changed the value you were returning from mockService.get as follows:

mockService.get.and.returnValue(of({entities: ITEMS, total: 2}));

The reason for this is your component expects there to be an 'entities' key in the result object with the value of the items. Note - it also expects there to be a 'total' key as well, so I added that as well though you were not testing for it.

Another thing to note, which I changed in the StackBlitz to demonstrate. While your tests will all pass as you wrote them, you may not be aware that fixture.detectChanges() actually executes ngOnInit() - that has tripped me up in testing before. To show this I modified where you had specifically called component.ngOnInit() in one spec and where you called component.updateItems() in this spec and replaced them with fixture.detectChanges(). Both will work fine of course, but I point this out because in some testing you'll need to set the mock BEFORE calling ngOnInit() to get valid data, and putting fixture.detectChanges() in the beforeEach() above all the specs means it gets called every time BEFORE each spec is called.

I hope this helps.

dmcgrandle
  • 5,934
  • 1
  • 19
  • 38
  • omg! thank you very very much, you solved hours of my painful reflections, trials and errors! – korn Jan 10 '19 at 07:09