Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: fakePromise - collapsing/synchronizing promise chains #1800

Closed
Berzeg opened this issue May 20, 2018 · 4 comments
Closed

Feature Request: fakePromise - collapsing/synchronizing promise chains #1800

Berzeg opened this issue May 20, 2018 · 4 comments

Comments

@Berzeg
Copy link

Berzeg commented May 20, 2018

EDIT: OK, so I think my issue is rooted in the fact that jest's fake timers change timer behaviours. More specifically, using fake timers no longer enables you to schedule callbacks to be called after all promise chains are executed.

I'll create an issue for the jest folks instead. Not sure fakePromise is of use to anyone otherwise 😅

Motivation

I have an object that has a timer that triggers a network request, and the returned response is passed through a promise chain (multiple .then() calls). The final callback in the promise chain is supposed to change my object's state.

class SomeObject {
  change() {
    setTimeout(() => {
      this.submit();
    }, 500);
  }

  submit() {
    requests.postRequest()
      .then((res) => {
        return res.json();
      })
      .then((json) => {
        this.someState = json.someProperty;
      });
  }
}

I want to test that a certain change is later on reflected in the object's state. The way I set out implementing this at first is by using a fakeServer to return the expected response, then I trigger the change, waiting for network request to be made (setImmediate), waiting for the promise chain to complete (setImmediate again), and then testing the state. Theoretically it should all work out. Wonderful!

Except, I forgot to mention that I'm using Timer Mocks in jest, which flatten and synchronize the timers. Usually, you could call setTimeout(callback) and your callback is guaranteed to be called after all promises, but this is no longer the case here. Back to the drawing board...

Solution

Ok, so the solution I arrived at is to modify the request function (requests.postRequest() in the above example) with a function that returns a synchronous promise. That implies that if you sandwich the promise between two synchronous tasks then the promise will be complete before the 2nd synchronous task:

syncTaskA();
fakePromise(); // all then() callbacks are called before syncTaskB
syncTaskB();

The implementation of a synchronous promise is pretty simple:

fakeResolve(val) {
  const promise = {
    then: (cb) => {
      return cb(val);
    }
  }
  return promise;
}

For the above example I can mock requests.postRequest like so:

// test.js

requests.postRequest = () => {
  const res = {
    json: () => fakeResolve({someProperty: someValue});
  };
  return fakeResolve(res);
};

someObject = new SomeObject();
someObject.change();
setTimeout(() => {
  assert someObject.someState === expectedState;
}, 1000);
syncTimers(); // all timers time out at this point

Alternatives

The most obvious alternative is placing your test at the end of a promise chain that's longer than the one you're trying to test (as mentioned here). This works! Moreover, my solution changes the behaviour of promises. For example:

Promise.resolve()
  .then(() => {
    console.log(1);
    return Promise.resolve();
  })
  .then(() => {
    console.log(2);
  });

Promise.resolve()
  .then(() => {
    console.log(3);
    return Promise.resolve();
  })
  .then(() => {
    console.log(4);
  });

// Prints
// 1
// 3
// 2
// 4

fakeResolve()
  .then(() => {
    console.log(1);
    return fakeResolve();
  })
  .then(() => {
    console.log(2);
  });

fakeResolve()
  .then(() => {
    console.log(3);
    return fakeResolve();
  })
  .then(() => {
    console.log(4);
  });

// Prints
// 1
// 2
// 3
// 4

So, if you somehow had code with interdependent promises then this might cause unexpected behaviour. Still, I find a chain of promises to be less clear than using fake promises for something like a network response.

@Berzeg
Copy link
Author

Berzeg commented May 20, 2018

Note: the implementation I had in mind is more like a fakePromise class with a static resolve method and a static reject method. I'm just gauging interest in synchronous promises before going into details / presenting a pull request.

@fatso83
Copy link
Contributor

fatso83 commented May 21, 2018

what was the problem? or just sharing?

@Berzeg Berzeg changed the title fakePromise: collapsing/synchronizing promise chains featureRequest: fakePromise - collapsing/synchronizing promise chains May 21, 2018
@Berzeg Berzeg changed the title featureRequest: fakePromise - collapsing/synchronizing promise chains Feature Request: fakePromise - collapsing/synchronizing promise chains May 21, 2018
@Berzeg
Copy link
Author

Berzeg commented May 21, 2018

@fatso83 it's a feature request for a fakePromise class that would synchronize promise chains.

@Berzeg
Copy link
Author

Berzeg commented May 21, 2018

OK, so I think my issue is rooted in the fact that jest's fake timers change timer behaviours. More specifically, using fake timers no longer enables you to schedule callbacks to be called after all promise chains are executed.

I'll create an issue for the jest folks instead. Not sure fakePromise is of use to anyone otherwise 😅

@Berzeg Berzeg closed this as completed May 21, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants