Search code examples
angularangular-test

How to test Angular form statusChanges observable?


I have an Angular component that takes an NgForm as an input, it can be modfied and when it becomes valid it will emit an object.

@Input() form!: IQuestionForm; //this type extends NgForm;
@Input() question!: IQuestion;
@Output() questionChange = new EventEmitter<IQuestion>();

ngOnInit(): void {
  this.form.form.statusChanges.pipe(filter((s) => s === 'VALID')).subscribe(() => {
    this.questionChange.emit(this.question);
  });
}

This works, but I am struggling with how to verify this behavior in a unit test.

const mockQuestionForm = new NgForm([], []) as IQuestionForm;
mockQuestionForm.form.addControl('questionText', new FormControl('', Validators.required));
mockQuestionForm.form.addControl('responseType', new FormControl(QuestionTypeEnum.FREE_TEXT, Validators.required));

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

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [QuestionFormEditorComponent],
    });
    fixture = TestBed.createComponent(QuestionFormEditorComponent);
    component = fixture.componentInstance;
  });
  
  it('should emit the updated question object when the form becomes valid', () => {
    spyOn(component.questionChange, 'emit');
    
    component.form = { ...mockQuestionForm } as IQuestionForm;
    component.question = {
      id: '',
      text: '',
      type: QuestionTypeEnum.FREE_TEXT,
    };
    
    fixture.detectChanges(); //triggers ngOnInit()
    
    expect(component.questionChange.emit).not.toHaveBeenCalled();
  
    // ???
  
    expect(component.questionChange.emit).toHaveBeenCalledOnceWith();
  });
  
  it('should NOT emit the updated question object when the form becomes invalid', () => {
    //similar test here
  });
});

Am I on the wrong track here? How should I go about testing this behavior?


Solution

  • This ended up working

    it('should NOT emit the updated question object when the form becomes invalid', () => {
      spyOn(component.questionChange, 'emit');
      fixture.detectChanges(); //triggers ngOnInit()
    
      //Adding an error marks it as invalid
      component.form.form.controls['questionText'].setErrors({ required: true });
    
      expect(component.questionChange.emit).not.toHaveBeenCalled();
    });
    
    it('should emit the updated question object when the form becomes valid', () => {
      spyOn(component.questionChange, 'emit');
      fixture.detectChanges(); //triggers ngOnInit()
    
      expect(component.questionChange.emit).not.toHaveBeenCalled();
    
      //Adding an error marks it as invalid
      component.form.form.controls['questionText'].setErrors({ required: true });
      //remove errors marks it as valid
      component.form.form.controls['questionText'].setErrors(null);
    
      expect(component.questionChange.emit).toHaveBeenCalledOnceWith(component.question);
    });