Let’s say I have the following class:

class Foo {
    public a( a, b ) {
        return this._execute( {a,b} );
    }
    
    public b( a ) {
        return this._execute( a );
    }

    private _execute( a ) {
        if ( ...condition... ) {
            throw new Error("Validation failed!"); 
        }

        return a;
    }
}

And I’d like to prepare a set of unit tests for it. Should I add a test that should check the condition both for a() and b()? Maybe only one method should check it? If so, which one? Or maybe I should use some other approach?

My goal is to reduce the number of unit tests and in this case, I’ve got the same test for both methods.

Answer

You can split your code into components that are dealing with one thing only:

class Foo {
    private _validator;
    
    constructor(validator) {
      this._validator = validator;
    }
    
    public a( a, b ) {
        return this._validator.validate( {a,b} );
    }
    
    public b( a ) {
        return this._validator.validate( a );
    }
}

class MyValidator {
  public validate( value ) {
      if ( /*...condition...*/ ) {
          throw new Error("Validation failed!"); 
      }

      return value;
  }
}

Now you can test MyValidator as much as you want to make sure it handles inputs as you want. The tests will be applicable for both Foo#a and Foo#b.

What you want to do next is to verify that the methods do use the validator, so in a test setup you can create a mock validator and instantiate const foo = new Foo( mockValidator ). Then the test can look like this:

test( "a() uses the validator", () => {
    //arrange
    const mockValidator = { validate: jest.fn() };
    const foo = new Foo( mockValidator );
    const value = "something";
    
    //act
    foo.a( value );
    
    //assert
    expect(mockValidator.validate).toHaveBeenCalledWith(value);
}

test( "a() does not swallow the validation error", () => {
    //arrange
    const mockValidator = { validate: () => { throw Error("mock") } };
    const foo = new Foo( mockValidator );
    
    //act + assert
    expect(() => foo.a( "" )).toThrow();
}

Note: I’m using Jest for illustrative purposes. The same can be achieved using other testing libraries.