puddlejumper26 / blogs

Personal Tech Blogs
4 stars 1 forks source link

Unit test a component which has type extends from other component's type #3

Open puddlejumper26 opened 4 years ago

puddlejumper26 commented 4 years ago

Assuming we have two components, AppleComponent and BananaComponent, and we need to write test for AppleComponent.

AppleComponent

@Component({
    selector: 'Apple-Component',
    styleUrls: ['./apple.component.scss'],
    templateUrl: './apple.component.html',
})
export class AppleComponent<T,U extends BananaComponent<T>>{
   public appleA = 'appleA';
   public appleB = 'appleB';
   public applePurchase !: U;
}

BananaComponent

@Component({
    selector: 'Banana-Component',
    styleUrls: ['./banana.component.scss'],
    templateUrl: './banana.component.html',
})
export abstract class BananaComponent<T> implements OnInit{
   public bananaA = 'bananaA';
   public bananaB = 'bananaB';
   public bananaPurchase : T;

   public totalPurchase: T;
}

And spec test file for AppleComponent

spec file

import { CommonModule } from '@angular/common';
import { ComponentFixture, TestBed } from '@angular/core/testing';

import {AppleComponent} from '....';
import {BananaComponent} from '....';

describe('AppleComponent', () => {
   let component: AppleComponent;
   let fixture: ComponentFixture<AppleComponent>;

   beforeEach(()=>{
     TestBed.configureTestingModule({
         declarations:[AppleComponent],
         imports:[ all imports from apple.component.module.ts],
     });
     fixture = TestBed.createComponent(AppleComponent);
     component = fixture.componentInstance;
   });

   it('should with defaults',() => {
      expect(component.appleA).toBe('appleA','appleA has appleA as default value'); // passes
      expect(component.bananaA).toBe('bananaA','bananaA has bananaA as default value'); // failes
   });
});

The above code is totally fine if AppleComponent has no type, but since we have <T,U extends BananaComponent> defined in AppleComponent, so the above spec test code will not work.

Solution

// first inside the test to define a random type, then give it to AppleComponent, and BananaComponent.

type typeA = { };  
type typeModel = AppleComponent<typeA, BananaComponent<typeA>>;   

let component: typeModel; 
let fixture: ComponentFixture<typeModel>

Explaination

What is happnening here is just the type of AppleComponent extends the BananaComponent; The purpose is to keep the type of the input value inside the AppleComponent to be identical with the type of input value inside BananaComponent.

And for a better understanding of the relationship between the types of AppleComponent and BananaComponent, here are the more detailed codes.

AppleComponent

export class AppleComponent<T,U extends BananaComponent<T>>{
   public appleA = 'appleA';
   public appleB = 'appleB';
   public applePurchase !: U;
}

BananaComponent

export abstract class BananaComponent<T> implements OnInit{
   public bananaA = 'bananaA';
   public bananaB = 'bananaB';
   public bananaPurchase : T;

   public totalPurchase: T;
}

from the above code, we could see that

  1. inside BananaComponent, type T is to make sure the input bananaPurchase has the same type with totalPurchase, e.g. when bananaPurchase = 1000; then the type is set to number, then the value of totalPurchase has to be a number as well, and vice versa.

  2. same situation inside AppleComponent, because it extends the type from BananaComponent, therefore it means the applePurchase should have the same type with bananaPurchase and totalPurchase.


This is actually coming from a post of mine on stackoverflow

For more info, please take How to test an @Input varaible inside Angular Testing as reference