How to match TypeScript optional parameter in Jasmine?

Viewed 135

I'm refactoring a TypeScript method to add an optional parameter with a default value. The refactored method is a core operation, and there are many high-level operations that call the core function. The pre-existing calls omit the new parameter (thus using the default value), while new and refactored methods supply the new parameter value. A simplified version looks like this:

export class Scratch {
  coreOperation(mainArg: string, option: boolean = false) {
    // ...
  }

  private midLevelOperation(mainArg: string) {
    this.coreOperation(mainArg + '1');
  }

  highLevelOperation1(mainArg: string) {
    this.midLevelOperation(mainArg);
    this.coreOperation(mainArg + '2', true);
  }
}

I'm also updating the Jasmine tests of the higher-level operations. I want to assert that the high-level operations cause the core operation to be called with certain parameters. Tests would look roughly like this:

describe('Scratch', () => {
  let objectUnderTest: Scratch;

  beforeEach(() => {
    objectUnderTest = new Scratch();
    spyOn(objectUnderTest, 'coreOperation');
  });

  describe('highLevelOperation1', () => {
    it('should call core operation', () => {
      objectUnderTest.highLevelOperation1('main');
      expect(objectUnderTest.coreOperation).toHaveBeenCalledWith('main1', false);
      expect(objectUnderTest.coreOperation).toHaveBeenCalledWith('main2', true);
    });
  });
});

The problem is that Jasmine's toHaveBeenCalledWith doesn't know that the second argument has a default value. The error for the above code:

  - Expected spy coreOperation to have been called with:
    [ 'main1', false ]
  but actual calls were:
    [ 'main1' ],
    [ 'main2', true ].

Obviously I can get the test to pass by removing the the false argument from my test. But I don't want the tests to know whether call sites use 1 argument or 2, especially from private library functions like this example.

Is there a way to write a Jasmine matcher that works both when an optional parameter is omitted and when the default value is passed?

1 Answers

Unfortunately there is no way to spy on default/optional parameters in Jasmine.

There are a couple of reasons why this is not possible:

  1. Default parameters are set in the arguments array variable that exists in the scope of the invoked method and they are not actual values that get passed into parameters.
  2. toHaveBeenCalledWith can only test what arguments the method was called with, not what values the method has decided to default its missing arguments to.

To get around this problem you can wrap the target method inside another method that contains the option: boolean = false default parameter. Then you should be able to inspect what coreOperation was called with:

coreOperation(mainArg: string, option: boolean) {
  // ...
}

private coreOperationDefaultWrapper(mainArg: string, option: boolean = false) {
  this.coreOperationmainArg(mainArg, option);
}

It is worth noting that best tests are ones that test what the application does rather than how it is written. Hence I would strongly recommend exercising the mindset of testing what behaviour the default arguments would result in rather than checking what they actually are. Ofcourse this is not always possible and I hope the above example can help you! :)

Related