question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Dealing with Promises

See original GitHub issue

First, thanks for the lib! I’ve successfully written several tests, and it’s very effective.

I hit a wall recently though when attempting to test an observable that mergeMaps a Promise (in my case, one that waits on a network request, which I’ve stubbed in the test with nock).

Simplified example:

  it('promises', marbles(m => {
    const obs1 = Observable.from(['a','b','c']).mergeMap(x => Observable.of(x))
    m.equal(obs1, '(abc|)') // Works

    const obs2 = Observable.from(['a','b','c']).mergeMap(x => Promise.resolve(x))
    m.equal(obs2, '(abc|)') // Fails
  }))

The second assertion fails with:

     Error:
Expected

to deep equal
	{"frame":0,"notification":{"kind":"N","value":"a","hasValue":true}}
	{"frame":0,"notification":{"kind":"N","value":"b","hasValue":true}}
	{"frame":0,"notification":{"kind":"N","value":"c","hasValue":true}}
	{"frame":0,"notification":{"kind":"C","hasValue":false}}

I assume this is because Promises (the native implementation, anyway) always resolves on the next tick, while marble testing is intended to test the observable within one tick.

Is my only option to do something hacky like mock the function that returns a promise to return a Observable.of() stubbed value instead?

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Reactions:2
  • Comments:9 (6 by maintainers)

github_iconTop GitHub Comments

5reactions
cartantcommented, Dec 7, 2017

Yep. You are correct.

The problem is that promises always resolve asynchronously, so your mock is still asynchronous and not compatible with the TestScheduler - which runs the subscriptions in a synchronous manner when its flush method is called (usually at the end of each test). That’s why the Expected output is empty - the test has finished before the promise resolved.

You are also correct in that the solution would involve mocking the promise with something the TestScheduler understands and can treat in a synchronous manner (either a synchronous observable - like Observable.of - or an asynchronous observable that takes the TestScheduler instance as a parameter).

Being able to use marble tests with observables that cannot be made to use virtual time is something that I have on my list of problems to solve, but I’ve not yet devoted any time to it. I have a large number of mocked Firebase tests - written about 12 months ago - that would have been much easier to write using marble tests. Finding a solution is something that will likely wait until I again need to write a substantial number of tests for non-virtual-time observables.

1reaction
nmccreadycommented, Oct 5, 2018

Ok, I think I was looking more for direction in how asynchronous stuff is handled within the marble callback. In jest it still supports returning promises. So this is exactly what I needed.

const bindMouseEvents = jest.fn(); // injected into module
describe('some epics', () => {
  it(
    viewerMouseEvents.name,
    marbles((m) => {
      const action$ = m.hot('-a-|', {
        a: { type: 'SOME_EVENT', payload: {} }
      });

      // ignore elements strips all elements but errors and completes
      const expected = m.cold('---|');

      const state$ = {};

      const out = viewerMouseEvents(action$, state$);
      m.expect(out).toBeObservable(expected);
      return out
        .toPromise() // don't miss and make sure stuff internally is called
        .then(() => expect(bindMouseEvents).toHaveBeenCalled());
    })
  );
});

Hope this helps someone else.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Dealing with Promises in JavaScript | by Travis Waith-Mair
Once again it's important to emphasize that Promises represent asynchronous action. It's called a promise because it “promises” to resolve or ...
Read more >
Using promises - JavaScript - MDN Web Docs
Essentially, a promise is a returned object to which you attach callbacks, instead of passing callbacks into a function.
Read more >
JavaScript Promise Tutorial – How to Resolve or Reject ...
Just to review, a promise can be created with the constructor syntax, like this: let promise = new Promise(function(resolve, reject) ...
Read more >
JavaScript Promises: an introduction - web.dev
Promises simplify deferred and asynchronous computations. A promise represents an operation that hasn't completed yet.
Read more >
Deal with Promises like a Pro - DEV Community ‍ ‍
The Promise.all takes an iterable of promises as input and then returns a single promise that'll resolve into an array of the results...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found