ngOnChanges not called in Angular 4 unit test detectChanges()
Asked Answered
M

2

12

The question

I have a button component that accepts a promise and disables the button until the promise has resolved and I want to write a unit test for this functionality.

My Code

My button component has an input for the promise

  /**
   * A single promise that triggers the busy state until it resolves
   */
  @Input()
  public promise: Promise<any>;

Inside ngOnChanges I listen for the promise

/**
 * Enable button busy state until promise resolves
 */
if (changes && changes.promise && changes.promise.currentValue) {
  this.busyUntilPromiseResolves(changes.promise.currentValue);
}

Then I store an array of active promises so multiple promises can be passed in

  /**
   * Add a promise that will keep the button in a busy state until it resolves
   * @param activityProimise
   */
  private busyUntilPromiseResolves(activityProimise) {
    this.activityPromises.push(activityProimise);
    activityProimise.then(() => {
      this.activityPromises.splice(this.activityPromises.indexOf(activityProimise), 1);
    });
  }

Then finally in my template I disable the button if there are any promises in the array.

[disabled]="activityPromises.length > 0"

Help me obi-wan

I have been dabbling and trying different things to make this work, this is my current test code that doesn't work. Basically I need to check that the button is disabled before the promise resolves, and I will then want to do another test that checks it is re-enabled after the promise resolves.

  it('should be disabled when passed a single promise', async((done) => {
    let promise;

    return promise = new Promise(resolve => {
      component.promise = promise;
      fixture.detectChanges();
      expect(buttonElement.disabled).toBe(true);
      return resolve();
    });
  }));

As always any help will be appreciated, thanks.

Mundane answered 3/1, 2018 at 22:31 Comment(0)
M
15

The short answer to this question is that ngOnChanges doesn't get fired automatically during unit tests so I had to call it manually.

it('should be disabled when passed a single promise', async () => {
    let promise;
    let resolve;

    // should not be disabled here
    expect(buttonElement.disabled).toBe(false);

    // Pass a promise to the component to disable it
    promise = new Promise((r) => (resolve = r));
    component.ngOnChanges({
        promise: new SimpleChange(null, promise, null),
    });

    fixture.detectChanges();
    expect(buttonElement.disabled).toBe(true);

    // now resolve the promise and make sure it's enabled again.
    promise.then(() => {
        fixture.detectChanges();
        expect(buttonElement.disabled).toBe(false);
    });

    resolve();
});
Mundane answered 4/1, 2018 at 3:24 Comment(0)
T
4

Have you tried asserting before adding that promise?

it('should be disabled when passed a single promise', async(() => {
  let promise;
  // should not be disabledhere
  expect(buttonElement.disabled).toBe(false);
  let resolve;
  const promise = new Promise(r => resolve = r);
  component.promise = promise;
  component.ngOnChanges(); // Call this here, TestBed doesn't know it needs to.
  fixture.detectChanges();
  expect(buttonElement.disabled).toBe(true);

  // now resolve the promise and make sure it's disabled again.
  resolve();
  fixture.detectChanges();
  expect(buttonElement.disabled).toBe(false);
}));

Edit: As noted in the comments, the way you set that promise property doesn't work with TestBed, so it never calls your ngOnChanges() method. You could call it directly, like I did above.

Alternatively, wrap your test component in a host component:

@Component({
  selector: 'test-component',
  template: `<my-component [promise]="promise"></my-component>`
})
class TestComponent {
  promise;
}

// then use that in your test instead:
const wrapperCmp = TestBed.createComponent(TestComponent);
wrapperCmp.promise = promise; // and check for the button state before and after all the changes.
Tichon answered 3/1, 2018 at 23:29 Comment(2)
Heya! thanks for the suggestion. Unfortunately this gives me "Expected false to be true." for "expect(buttonElement.disabled).toBe(true);"Mundane
That is due to how your component works, probably a separate question. I've added a call to ngOnChanges() manually here (TestBed doesn't call ngOnChanges when you change properties directly like this), or the better solution would be to wrap the component in a host component.Tichon

© 2022 - 2024 — McMap. All rights reserved.