Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ngOnChanges not called in Angular 4 unit test detectChanges()

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.

like image 569
PaulParton Avatar asked Jan 03 '18 22:01

PaulParton


People also ask

Does fixture detectChanges call ngOnChanges?

This means that when we call detectChanges Angular will not detect any changes and not call ngOnChanges. So, how can change our test so ngOnChanges is called? wrap Greeter in a host component on which we can change the name property.

What is the purpose of the fixture detectChanges () call in this unit test?

Fixtures have access to a debugElement , which will give you access to the internals of the component fixture. Change detection isn't done automatically, so you'll call detectChanges on a fixture to tell Angular to run change detection.

How is ngOnChanges called?

ngOnChanges gets called before ngOnInit and whenever a component's bound input is changed FROM THE PARENT COMPONENT. Remember that ngOnChanges is specific to bound inputs on the component. This means if you don't have any @Input properties on a child, ngOnChanges will never get called.

Does detectChanges call ngOnInit?

detectChanges() tells Angular to run change-detection. Finally! Every time it is called, it updates data bindings like ng-if, and re-renders the component based on the updated data. Calling this function will cause ngOnInit to run only the first time it is called.


2 Answers

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();
});
like image 101
PaulParton Avatar answered Oct 22 '22 14:10

PaulParton


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.
like image 42
Zlatko Avatar answered Oct 22 '22 14:10

Zlatko