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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calling verify on rejected function fails test #483

Closed
3 tasks done
JuanCaicedo opened this issue Apr 8, 2022 · 4 comments
Closed
3 tasks done

Calling verify on rejected function fails test #483

JuanCaicedo opened this issue Apr 8, 2022 · 4 comments

Comments

@JuanCaicedo
Copy link

Hi again! Let me know if I can provide more info around this error or if I'm misunderstanding something about the lib 馃榾

Description

I would like to stub a call so that it models a promise rejection. Afterwards I would like to verify that function was called correctly.

Issue

Verifying the rejection causes my test to fail asynchronously. Since the stub was called as expected, I would expect my test to fail

Environment

  • node -v output: v16.14.0
  • npm -v (or yarn --version) output: 8.3.1
  • npm ls testdouble (or yarn list testdouble) version: 3.16.4

Runkit Notebook

https://replit.com/@JuanCaicedo1/TD-rejections#index.ts

Code-fenced Examples

import * as td from 'testdouble'

const API = {
  async run(flag: string) {
    if (flag === 'should fail') {
      return Promise.reject('fail')
    }
    return Promise.resolve('success') 
  }
}

async function runSafely(flag: string): Promise<string> {
  try {
  const value = await API.run(flag)
    return value
  } catch (err) {
    return err as string
  }
}

  
td.replace(API, 'run')
td.when(API.run('should fail')).thenReject('fail')

async function test(): Promise<string> {
  const value = await runSafely('should fail')
  td.verify(
    await API.run('should fail')
    // Also tried API.run('should fail')
  )
  return value
}

test()
  .then(result => console.log('result', result))
  .catch(err => console.log('error', err))
@searls
Copy link
Member

searls commented Apr 13, 2022

My first reaction was to think "we throw a warning when verifying invocations that are also stubbed" and indeed, when changed to td.verify(API.run('should fail')) (without await) is invoked, that warning is printed:

Warning: testdouble.js - td.verify - test double `run` was both stubbed and verified with arguments ("should fail"), which is redundant and probably unnecessary. (see: https://github.com/testdouble/testdouble.js/blob/main/docs/B-frequently-asked-questions.md#why-shouldnt-i-call-both-tdwhen-and-tdverify-for-a-single-interaction-with-a-test-double )

It also prints the failure:

result fail

And the uncaught promise error:

[UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "fail".] {
  code: 'ERR_UNHANDLED_REJECTION'
}

Here's my REPLit fork to verify that. Am I missing something here?

@JuanCaicedo
Copy link
Author

I guess I'm confused about the uncaught promise error. It seems to me like runSafely handles the rejected promise from API.run(). Since test is executing successfully and logging its result in .then(), so I would assume there would be no unhandled rejections.

But the top level unhandled rejection seems to break my test suite 鈽癸笍 In the repl you shared, I interpret the exit status 1 as a sign that the test suite is failing (and this matches the behavior in my mocha tests).

Does that help? Can I provide any more info? 馃榾

@JuanCaicedo
Copy link
Author

I found that I can fix my tests by explicitly catching the error generated by this verify call.

  td.verify(
    API.run('should fail').catch(err => null)
  ) 

This will still fail the test if API.run does not get called as expected, but stops the rejection call from failing the test. It seems a little surprising / magic to have this scattered throughout my test code though 馃槄

@searls
Copy link
Member

searls commented Apr 18, 2022

Yes, this is happening to you as a side effect of stubbing what you're verifying. First the test stubs td.when(API.run('should fail')).thenReject('fail'), but that means the subsequent demonstration of that call inside the td.verify() that satisfies the stubbing will also return a rejected promise, which in turn Node will detect as being uncaught/unhandled.

The real solution IMO is to address the td.js warning and not stub what you're also verifying, by instead adding a normal assertion that indirectly demands the stubbing be used as expected. This is how I would change it:

td.replace(API, 'run')
td.when(API.run('should fail')).thenReject('fail')

async function test(): Promise<string> {
  const value = await runSafely('should fail')

  assert.strictEqual(value, 'fail')
  return value
}

test()
  .then(result => console.log('result', result))
  .catch(err => console.log('error', err))

@searls searls closed this as completed Apr 18, 2022
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