3

In our Angular porject, We are migrating jasmine tests to jest. We are getting some issues when we try to mock @Input values of components in the tests. For example, in jamsine we use to write something like this :

@Component({
  selector: 'app-message[message]',
  templateUrl: './message.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class MessageComponent implements OnInit {
  @Input() message!: Message;
}


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

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [// some imports],
      declarations: [MessageComponent]
    }).compileComponents();
  });

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

  it('test1 with input message') {
     component.message = new Message('xxx');
     fixture.detectChanges();
     // some expectations
  }

  it('test2 with another input message') {
     component.message = new Message('yyy');
     fixture.detectChanges();
     // some expectations
  }
});

With jasmine, these tests were always passing and the instance of Message was always set like this in the tests. But with jest, everytime fixture.detectChanges() is called, the component is reset and all the @Input values are set to undefined and I do not know how to deal with it. I then try another way for doing it insprired by (Angular Unit-Test) How to mock input property in Jasmin? and I now use a HostComponent to call my template like

let testMessage: Message = generateMessage();
@Component({
  selector: 'app-testhost',
  template: `<app-message [message]="message"></app-message>`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class FakeHostComponent {
  message = testMessage;
}

It works fine if I have only 1 test but I do not know how I can change the value of "message" dynamically in each tests. I feel like I'm missing something or doing something wrong. Should I create a FakeHostComponent for each of my tests ?
So I need help to make these tests a bit prettier and more dynamics. Also I would like to understand why fixture.detectChanges() reset my component input values
As a workaround, I put the value of message in a global variable "testMessage" and create this method that I call in every test, but I am sure there is a way to do it more properly

const createComponent = (message: Message) => {
    testMessage = message;
    fixture = TestBed.createComponent(FakeHostComponent);
    component = fixture.debugElement.children[0].componentInstance;
  };
TCH
  • 421
  • 1
  • 6
  • 25

2 Answers2

1

I too was struggling with Input, as it would always be undefined. I tried mocking it in the beforeEach section, but whatever I do, the input value stays undefined and I'm unable to get into the component.

Removing fixture.detectChanges() finally removed console error and tests pass. Not really sure why, but this topic helped!

fadingbeat
  • 355
  • 3
  • 16
0

That is strange that @Input values reset with fixture.detectChanges().

With FakeHostComponent, to get a handle on the message value, you can do something like this:

import { By } from '@angular/platform-browser';
....
let component: FakeHostComponent;
let messageComponent: MessageComponent;

beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [// some imports],
      declarations: [MessageComponent, FakeHostComponent]
    }).compileComponents();
  });

beforeEach(() => {
    fixture = TestBed.createComponent(FakeHostComponent);
    component = fixture.componentInstance;
    messageComponent = fixture.debugElement.query(By.directive(MessageComponent)).componentInstance;
    // now you have a handle on FakeHostComponent and MessageComponent
    component.message = 'Hello'; // will change from FakeHostComponent
    fixture.detectChanges();
    console.log(messageComponent.message); // should output the message
  });

AliF50
  • 16,947
  • 1
  • 21
  • 37
  • 1
    Hi, thanks for your answer. I tried your idea ! However this produces the same result : if I `console.log(messageComponent.message)` before `fixture.detectChanges()`, it prints 'Hello', but if I print it after, it prints undefined. If I set an input value when I create the component template: ``, then this message is printed after `fixture.detectChanges()`. It seems like it totally reset the component to the initial input values.... – TCH Jun 01 '21 at 08:28