0

In my code I have a method that returns a part object, however it returns the object inside a setTimeout method. When I attempt to test this I'm running into the issue that the test does not wait until the timeout has completed before it evaluates the response for the method. How can I go about telling the test to wait for the timeout to complete before it checks the expectation?

As you can see the getIntersection method returns this.part inside of a timeout.

public getIntersection(payload: DropPayload): Part {
  let rect: ClientRect = this.element.nativeElement.getBoundingClientRect();
  if (rect.left < payload.event.pageX && rect.right > payload.event.pageX && rect.top < payload.event.pageY && rect.bottom > payload.event.pageY) {
    setTimeout(() => {
      this.changeRef.detectChanges();
      return this.part;
    });
  }
  return null;
}

Here's a few things I've tried based on researching this issue, none of them work:

1:

it('return the part if the payload coordinates are inside the bounding box', async((done) => {
  partInstance.part = Clone.create(mockPart);
  let result: any = partInstance.getIntersection(mockPayload);
  setTimeout(() => {
    expect(result).toEqual(mockPart);
    done();
  });
}));

2:

it('return the part if the payload coordinates are inside the bounding box', async(() => {
  partInstance.part = Clone.create(mockPart);
  let result: any = partInstance.getIntersection(mockPayload);
  setTimeout(() => {
    expect(result).toEqual(mockPart);
  });
}));

3: (this one throws errors that runs isn't defined. I believe this is an Angular 1 solution)

it('return the part if the payload coordinates are inside the bounding box', () => {
  let done: boolean = false;
  let result: any;
  runs(() => {
    partInstance.part = Clone.create(mockPart);
    result = partInstance.getIntersection(mockPayload);
    setTimeout(() => {
      done = true;
    });
  });
  waitsFor(() => {
    return done;
  });
  runs(() => {
      expect(result).toEqual(mockPart);
  });
});

4:

it('return the part if the payload coordinates are inside the bounding box', () => {
  partInstance.part = Clone.create(mockPart);
  let result: any = partInstance.getIntersection(mockPayload);
  setTimeout(() => {
    expect(result).toEqual(mockPart);
  });
});
brijmcq
  • 3,354
  • 2
  • 17
  • 34
efarley
  • 8,371
  • 12
  • 42
  • 65

1 Answers1

0

Look at your logic

public getIntersection(payload: DropPayload): Part {
  let rect: ClientRect = this.element.nativeElement.getBoundingClientRect();
  if (rect.left < payload.event.pageX && rect.right > payload.event.pageX && rect.top < payload.event.pageY && rect.bottom > payload.event.pageY) {
    setTimeout(() => {
      this.changeRef.detectChanges();
      return this.part;
    });
  }
  return null;
}

Two things:

  1. The setTimeout happens asynchronously. But the return in the main function happens synchronously. The return value will always be null

  2. Returning in the callback of the setTimeout doesn't do what you think it does, i.e magically make the return value something else. What you return in the callback is not what is the return value for the main function.

You need to restructure your code, maybe to use promises. Have the method return a promise

public getIntersection(payload: DropPayload): Promise<Part> {
  let rect: ClientRect = this.element.nativeElement.getBoundingClientRect();

  return new Promise((resolve) => {
    setTimeout(() => {
      this.changeRef.detectChanges();
      resolve(this.part);
    })
  })
}

I have no idea what the logic behind your method is for, so I just left it out. But you will probably need to incorporate it back somehow.

Then you can call the function like

getIntersection().then(part => {
  expect(...)
})

Using your attempt with the Angular async should work for this.

Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720