angulartypescriptunit-testingkarma-jasmineangular-unit-test

(Angular Unit-Test) How to mock input property in Jasmin?


I am currently trying to mock input properties for an Angular unit test. Unfortunately, I can not get any further and repeatedly receive the following error message:

TypeError: Cannot read property 'data' of undefined

My HTML Template looks like this

<div class="container-fluid">
  <div class="row">
    <div class="col-12">
      <plot [data]="graph.data" [layout]="graph.layout"></plot>
    </div>
  </div>
</div>

My Component like this:

...
export class ChartComponent implements OnInit {

  @Input() currentChart: Chart;

  currentLocationData: any;

  public graph = {
    data: [
      {
        type: 'bar',
        x: [1, 2, 3],
        y: [10, 20, 30],
      }
    ],
    layout: {
      title: 'A simple chart',
    },
    config: {
      scrollZoom: true
    }
  };

  ...
}

My unit-test looks very basic for now, but still throws the mentioned error:

describe('ChartComponent', () => {

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

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ChartComponent],
      imports: [
        // My imports
      ]
    })
      .compileComponents();
  }));

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

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

I tried different ways to mock the data property and the currentChart @Input.

What is the right way to achieve this and fix the unit-test?


Solution

  • The input property works just like any variable. In your beforeEach you could set it to a value

    beforeEach(() => {
      fixture = TestBed.createComponent(ExplorerChartViewComponent);
      component = fixture.componentInstance;
      component.currentChart = someChart; // set input before first detectChanges
      fixture.detectChanges();
    });
    

    You can read more about this here. I prefer this approach.

    With my preferred approach you would have a TestHost component that would look something like

    @Component({
      selector: 'app-testhost-chart',
      template: `<app-chart [currentChart]=chart></app-chart>`, // or whatever your Chart Component Selector is
    })
    export class TestHostComponent {
      chart = new Chart();
    }
    

    Then change over to creating the new test host.

     declarations: [ChartComponent, TestHostComponent ],
    ...
    beforeEach(() => {
      fixture = TestBed.createComponent(TestHostComponent );
      component = fixture.debugElement.children[0].componentInstance;
      fixture.detectChanges();
    });
    

    However I think I see two other issues you might have. Especially since you are assigning the graph

    1. You're putting declarations: [ChartComponent], but creating fixture = TestBed.createComponent(ExplorerChartViewComponent); I would think it should TestBed.createComponent(ChartComponent), unless that is a copy/paste issue.
    2. Your html has <plot [data]="graph.data" [layout]="graph.layout"></plot> which indicates a plot component that you are not declaring. You will need to declare a component for plot. I'd suggest doing something very similar to the TestHostComponent but one that has all the same public properties as your real PlotComponent, that way you aren't bringing the real functionality and dependencies of PlotComponent into your unit test for ChartComponent.