gardni
gardni

Reputation: 1424

test values within a subscribe block of a reactive form module's auditTime method

I'm attempting to test the values inside of a .subscribe block within an angular4/typescript jasmine unit test that uses a rxjs auditTime method. I've read a lot of similar posts to this one that don't seem to get me inside that block.

I have the following example component:

address.component.ts:

export class AddressComponent implements OnInit {

  public form: FormGroup;

  constructor (
    private fb: FormBuilder
  ) {}

  public ngOnInit (): void {
    this.createForm();
  }

  public createForm (): void {
    this.form = this.fb.group({
      lineOne: [this.address.lineOne],
    });

    this.form.valueChanges.auditTime(500).subscribe(() => {
      this.address.lineOne = this.form.value.lineOne;
    });
  }
}

address.component.html:

<div class="address">
  <form [formGroup]="form">
    <div class="row">
      <div class="col-12 address-body">
        <div class="form-group-main">
          <div class="row">

            <div class="address-input col-12">
              <label for="address-line-one" [class.required]="isMandatory">Address Line 1</label>
              <input class="form-control" id="address-line-one" type="text" formControlName="lineOne">
            </div>

          </div>
        </div>
      </div>

      <div class="col-12 address-body">
        <display-address [body]="address.lineOne"></display-address>
      </div>

      <div class="col-12">
        <button class="btn btn-full-width btn-primary btn-sm btn-edit" id="address-edit" type="button" (click)="...">Edit</button>
      </div>

    </div>
  </form>
</div>

address.component.spec.ts:

describe('Address Component', () => {
  let component: AddressComponent;
  let fixture: ComponentFixture<AddressComponent>;

  beforeEach(async(() => {

    TestBed.configureTestingModule({
      declarations: [AddressComponent],
      imports: [ReactiveFormsModule],
      schemas: [NO_ERRORS_SCHEMA],
    }).compileComponents().then(() => {
      fixture = TestBed.createComponent(AddressComponent);
      component = fixture.componentInstance;
    });

  }));

  it('should update address', fakeAsync(() => {
    component.address = {lineOne: 'street'};
    fixture.detectChanges();

    //do something.. 

  }));

});

assuming the above has been setup correctly and i haven't missed something glaringly obvious which is why my tests never provide the correct results, i've tried modifying my test with the following ideas:

spying on auditTime and calling through:

spyOn(component.form.valueChanges, 'auditTime').and.callThrough();

spying on auditTime and calling fake function:

spyOn(component.form.valueChanges,'auditTime').and.callFake(()=> {
    return {
        subscribe: () => {}
    }
});

spying on auditTime and returning a value Observable.of:

spyOn(component.form.valueChanges,'auditTime').and.returnValue(Observable.of('hello'));

calling it directly:

component.form.valueChanges.auditTime().subscribe(result => {
   //never get in here
});

trying to wait using: tick() and trying to flush using: flushMicrotasks().

spying on the createForm function:

spyOn(component, 'createForm').and.callFake(() => {
    return Observable.of('hello');
});

using debugElement to access and update lineOne always returns errors if i use the following:

const el = fixture.nativeElement.querySelector(By.css('#address-line-one'));
el.value = 'something';
el.dispatchEvent(new Event('input'));
fixture.detectChanges();

as it would appear it can only see the edit button of the html and <display-address></display-address>, suggesting it's maybe not compiled at the time.

I've also tried wrapping everything in:

fixture.whenStable().then(() => {
    // do something.. 
});

and now i'm out of ideas, if anyone can make sense of the above and point me in the right direction, it would be great..


SOLUTION


Thanks to Amir, it would appear that in this particular example i had been massively overthinking the answers required for this, and using the NO_ERRORS_SCHEMA was hiding the true problem. It turns out i needed to import the component which <display-address> required:

imports: [ReactiveFormsModule, DisplayAddressComponent],

this allowed the template to compile correctly, meaning that i could see all the form values instead of just <display-address>. As recommended i used the combination of fakeAsync and tick(600) to emit change to the form:

it('should update address', fakeAsync(() => {
    component.address = {lineOne: 'street'};
    fixture.detectChanges();

    expect(component.address.lineOne).toBe('street');
    component.form.controls['lineOne'].setValue('test');
    tick(600);

    expect(component.address.lineOne).toBe('test');
  }));

just if anyone had a similar problem.

Upvotes: 1

Views: 1435

Answers (1)

Amir Arbabian
Amir Arbabian

Reputation: 3699

Ok i will just post here my assumptions, check if it helps:

  1. Call ngOnInit() directly or using detectChanges() right after you create component;
  2. Test should work synchronously only if you use fakeAsync + tick(600);
  3. After you performed tick(600) try to emit value into this.form.valueChanges, you can do that by substituting FormBuilder so it will return object with your own emitter (subject) or by finding an element in html and inputting value with DOM event.
  4. If you decide to use html for input, remove NO_ERRORS_SCHEMA and see maybe it's not rendered because of some errors (because you said that part of html was not present).

Upvotes: 1

Related Questions