2

I am using an attribute selector component called app-product-row on a tr tag and im passing product in an @Input() as follows;

class

@Component({
  selector: '[app-product-row]',
  templateUrl: './product-row.component.html',
  styleUrls: ['./product-row.component.scss']
})
export class ProductRowComponent {
  @Input() public product = null;
}

TestBed

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

import { ProductRowComponent } from './product-row.component';

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

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ ProductRowComponent ]
    })
    .compileComponents();
  }));

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

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

markup

<tr *ngFor="let product of searchResults"
    [product]="product"
    (showPipelineModalChange)="onShowPipelineModalChange($event)"
    app-product-row>
</tr>

the code compiles successfully upon ng serve and everything works as expected on the browser however unit tests are throwing

Chrome 63.0.3239 (Mac OS X 10.13.3)
Can't bind to 'product' since it isn't a known property of 'tr'

My implementation took inspiration from Angular2 table rows as component however in that example @Input()'s dont seem to be used and there is no mention of unit testing either.

could someone please point me in the right direction?

3 Answers3

2

This error usually happens when you haven't declared the component in your TestBed. Essentially, Angular doesn't recognize app-product-row as anything special, as if you had used it in your app without including it in your @NgModule. Try adding, in your test:

TestBed.configureTestingModule({
   declarations: [ProductRowComponent]
})

Each TestBed is kind of like a mini-module in the sense that you need to declare the components / directives you want to test.

Read more about setup here: https://angular.io/guide/testing#test-a-component

vince
  • 7,808
  • 3
  • 34
  • 41
  • thanks for replying vincecampanale. yup I have declared the component in the TestBed. I have updated my original question. not sure what else it could be? – omarCreativeDev Jan 25 '18 at 13:45
  • Can you post the code in `product-row.spec.ts` or recreate the issue with Stackblitz or something? The unit test probably doesn't need a `` to test the behavior of `ProductRowComponent`. – vince Jan 25 '18 at 13:56
  • have posted the spec. its pretty much generated from angular cli – omarCreativeDev Jan 25 '18 at 14:02
1

I was receiving the same error

Chrome 77.0.3865 (Windows 10.0.0) MyResultsComponent should create FAILED
    Can't bind to 'entry' since it isn't a known property of 'tr'. ("  <tr my-result [ERROR ->][entry]="entry" *ngFor="let entry of entries"><tr>"):

Due to having not included the attribute selector component MyResultComponent in the declarations

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ MyResultsComponent]
    })
    .compileComponents();
  }));

Adding MyResultComponent to the declarations resolved the test failure.

MyResults component typescript:

import { Component, OnInit } from '@angular/core';

class Entry {
  name: string;
  time: string;
}

@Component({
  selector: 'my-results, [my-results]',
  templateUrl: './my-results.component.html',
  styleUrls: ['./my-results.component.scss']
})
export class MyResultsComponent implements OnInit {

  entries: Entry[] = [
    { name: 'Entry One', time: '10:00'},
    { name: 'Entry Two', time: '10:05 '},
    { name: 'Entry Three', time: '10:10'},
  ];

  constructor() { }

  ngOnInit() {
  }
}

MyResults component template:

  <tr my-result [entry]="entry" *ngFor="let entry of entries"><tr>

Updated MyResults component test spec declarations:

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

import { MyResultsComponent } from './my-results.component';
import { MyResultComponent } from './../my-result/my-result.component';

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

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ MyResultsComponent, MyResultComponent ]
    })
    .compileComponents();
  }));

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

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

Attribute component MyResult typescript:

import { Component, OnInit, Input } from '@angular/core';

@Component({
  selector: '[my-result]',
  templateUrl: './my-result.component.html',
  styleUrls: ['./my-result.component.scss']
})
export class MyResultComponent implements OnInit {

  @Input() entry: any;

  constructor() { }

  ngOnInit() {
  }
}

Attribute component MyResult template:

  <td>{{ entry.name }}</td>
  <td>{{ entry.time }}</td>
Nicholas Murray
  • 13,305
  • 14
  • 65
  • 84
-1

a fellow front end dev has solved this problem for me! I had to declare the child component in the parent spec like so

TestBed.configureTestingModule({
  declarations: [
    SearchResultsListingComponent,
    ProductRowComponent
  ],
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
.compileComponents();