简体   繁体   中英

how to handle `Type | boolean` return values in typescript and error checking / tests

I have a function that can return a found object, or fail. What's the best value to return in the failure case? {}|null|undefined|false

The problem is my test rely on this object, but I get typescript warnings as false could also be returned

    const actualResult: ActionResult | boolean = await findAndRunAction(evt)
    if (actualResult) {
      expect(actualResult.klass).toBe('room')

where

function findAndRunAction(): ActionResult | false { 
// if found
return obj: ActionResult
// else
return false
 }

typescript is smart enough to know the value must be truthy but that should be when I am returning an instance of the full object, not just true .

I could throw an error, or the other way would be to always return the same object type but just add a ok: true|false field into the object. That seems a bit excessive, but I guess it does mean I always only have one return type...

Suggestions for good patterns to use with this in TS please!

ts问题

未通过测试

The compiler fails, but the actual test is passing.

Update: I did something like this:

// define all the other fields as optional

interface ActionResult {
  ok: boolean
  doc?: ActionData
  events?: string[]
  klass?: string
}

// so I can just return an empty obj in fail case

    return {
      ok: false
    }

I don't like this so much as it gives less rigidity on the actual type with all these options.

There's a couple ways you could go about this, as you're using a union type with a primitive, the simplest solution would be to typeof check for the boolean eg

  if (typeof actualResult !== 'boolean') {

The compiler is smart enough to narrow down the options to the single ActionResult . This works for primitives but only provides a shallow check so objects won't be properly differentiated.

Additionally this is quite unwieldy if you have more than two return types as you have to exclude each and everyone one (and chaining else if won't continually narrow down the types left you'd have to nest them inside which will become a confusing else if hellscape), so a (still not perfect) option is to implement your own typeof check with generics and property checking.

I got this snippet from how-to-use-typescript-type-guards and it's been handy a few times:

export const isOfType = <T>(
  varToBeChecked: any,
  propertyToCheckFor: keyof T,
): varToBeChecked is T =>
  (varToBeChecked as T)[propertyToCheckFor] !== undefined;

This little utility function will allow you to adjust your test to be:

test('should test', () => {
  const actualResult = findAndRunAction(false);
  if (isOfType<ActionResult>(actualResult, 'klass')) {
    expect(actualResult.klass).toBe('room');
  }
});

This will compile, but it is not perfect. We assume that klass is a unique property between the return types of your function, and it requires insight into the properties of the type you're checking for so its not a very abstracted solution, but might be helpful in some simple cases where the return is two differently structured objects.

In the case of something simple like this I'd probably just opt for the first option, but decide based on your needs:)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM