简体   繁体   中英

Unit testing of base class with jasmine and angular

I have one generic base class that I uses in each grid component.

Now, I have specs for grid component but I want to add specs for base class in separate base spec file.

Motive behind that is to remove repetitive code and make generic spec file for base grid which will be served for each grid that used base class.

I stuck at one point, I am not able to create a specs inside base spec file. Basically I want all the specs in base file under one function, and when this function called from sub class with that component, it should return all the specs for that class.

Here is what I have and what I tried so far.

Component Layer:

export class MyComponent extends BaseGrid<MyEntity> { 
    ... (and all other code like constructor and methods)... 
}

Base Layer:

export class BaseGrid<T> { 
    public async getData(): Promise<void> { ... }
}

... and 100 other functions

Component specs:

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

    beforeEach(async(() => {
        TestBed.configureTestingModule({
            declarations: [MyComponent],
            imports: [
                ...
        ],
            providers: [
            ],
        }).compileComponents().then(() => {
            fixture = TestBed.createComponent(MyComponent);
            component = fixture.componentInstance;
        });
    }));

    // here I created a reference function for my base class spec
    // Now, This is working but I don't want this **describe** and **it** to be here, 
    // it should be in the base file. so I can remove this repetitive code from all components.
    // And From here I just want to call one function let's say a **baseGridSpecs(component)**, 
    // that will load all the specs of base class in this component.
    describe('Should initialize Base grid', () => {
        it('should have proper component.', () => {
            const baseGridSpecs = new BaseGridSpecs<MyComponent>();
            baseGridSpecs.runBaseGridTests(component);
            baseGridSpecs.checkGetDataDefined(component);
        });
    });
});

Base specs:

export class BaseGridSpecs<T> {
  runBaseGridTests(component: any): void {
    expect(component).toBeTruthy();
  }

  checkGetDataDefined(component: any): void {
     expect(component.getData).toBeDefined();
  }
}

This structure works fine for me, but it has no use since my describe and it is still in main component spec file.

What I am trying to achieve is to just call the base spec function like baseGridSpecs.runBaseGridTests(component); and it should render all describe and it specs for given generic component.

Any help would be really appreciated...

Jasmine allows it definitions out of describe blocks and these definitions won't be run unless you call them explicitly from some describe block. Official documentation suggests two ways of using this fact.

The test subject can be shared with context but it requires you to use function instead of arrow functions and can be annoying and hard to track especially when there are many nested blocks, so your idea of having a class for specs makes sense.

Let's consider a simplified example with two classes. ( Playground )

// Base.ts  
export class Base {
  baseMethod() {
    return 0;
  }
}

and

// Derived.ts
import { Base } from "./base";

export class Derived extends Base {
  derivedMethod() {
    return 1;
  }
}

Tests for Derived class that check Base class parts can be encapsulated in BaseSpec :

// base.spec.ts
export class BaseSpec {
  public component;

  checkComponentInitialized(): void {
    it("should have proper component", () => {
      expect(this.component).toBeTruthy();
    });
  }

  checkBaseMethodDefined(): void {
    it("should have baseMethod method", () => {
      expect(this.component.baseMethod).toBeDefined();
    });
  }

  itActsLikeABase(): void {
    this.checkComponentInitialized();
    this.checkBaseMethodDefined();
  }
}

This class then can be used elsewhere

// derived.spec.ts
import { Derived } from "./derived";
import { BaseSpec } from "./base.spec";

describe("Derived", () => {
  describe("as a Base", () => {
    const baseSpec = new BaseSpec();

    beforeEach(() => {
      baseSpec.component = new Derived();
    });

    baseSpec.itActsLikeABase();
  });

  describe("as a Derived", () => {
    let component;
    beforeEach(() => {
      component = new Derived();
    });

    it("should have derivedMethod method", () => {
      expect(component.derivedMethod).toBeDefined();
    });
  });
});

Disclaimer

While it is technically possible and may be appropriate in some cases please think twice before writing tests this way. Inheritance is a powerful but often misused tool. The duplication because of testing shared behavior is most likely an accidental duplication since tests should not depend on how the behavior is implemented. Sharing behavior testing caveats

So with the help of Shalang, I was able to add a unit tests in my base grid spec file. Here is what I did.

Component Specs:

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

  beforeEach(async(() => {
      TestBed.configureTestingModule({
          declarations: [MyComponent],
          imports: [
              ...
      ],
          providers: [
          ],
      }).compileComponents().then(() => {
          fixture = TestBed.createComponent(MyComponent);
          component = fixture.componentInstance;
      });
  }));

  describe('Should initialize Base grid', () => {
    const baseGridSpecs = new BaseGridSpecs<ComponentModel>();

    beforeEach(() => {
      baseGridSpecs.component = component;
      baseGridSpecs.fixture = fixture;          
    });

    // Here for me dummyData is model's object with proper value, 
    // which I can use to check service based calls.
    baseGridSpecs.loadSpecs(dummyData);
  });
});

Base Specs:

export class BaseGridSpecs<T> {
  public component;

  public fixture;

  loadSpecs(dummyData: any): void {
    describe('Should initialize base component with Proper methods', () => {
      it('should initialize properly.', () => {
        expect(this.component).toBeTruthy();
      });

      it('should have Base getData function defined.', () => {
        expect(this.component.getData).toBeDefined();
      });
    });
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM