3

It seems to be fairly common opinion that you should only test public methods for a class or module and not private methods. If private methods are doing their job correctly, then that should be reflected in public methods.

That being said, when testing a public method that makes internal calls to private methods, should you test for things that could happen in that private method? Essentially, indirectly testing the private method?

Simplified example:

// Basic JavaScript module
let MyModule = function() {};
MyModule.prototype.taco = function() {
  ...
  burrito();
  ...
}

// Private function burrito
let burrito = function(){
  ...
  if (somethingHappened) {
    throw new Error('Oops!');
  }
  ...
}

In this case, the private method burrito is called from within the public taco method. In some situations, burrito can throw an error.

Should I write a test for taco where I am testing to see if an error ever gets thrown under certain situations? I'm not technically testing to see if the taco method itself throws an error, but rather if the underlying private method throws an error.

It's something I think is important to test, but since it's maybe bad practice to test private methods directly, is this the right way to test it? Or should I not test it at all? I just want to make sure that certain errors are thrown in a predictable manner.

For example, if burrito method makes an http request to some API server somewhere, but the server is offline at the time, it throws an error. It would be nice to write a test for this to ensure that when the server endpoint is offline, that the correct error is thrown.

4

3 Answers 3

4

Unit tests serve multiple purposes. In theory you should aim for 100% code coverage but in practice this doesn't always make sense (I.e not the best use of available resources). If you achieve 100% code coverage then by implication all code paths in all private methods have been executed, including your error case.

Another use for unit tests is to document all of the use cases envisaged by the original implementor. If you want to understand the capabilities and limitations of a class, reading the unit tests should tell you this much more effectively than reading the class itself. In this case if you envisage a use case where the consumer of your class should get an error response then you should document this use case by including a unit test for it.

It's not really about what is public vs what is private, the internal implementation of a class is allowed to change, and if the unit tests still pass then nothing should be broken by this refactoring. It is all about what is the expected behaviour of your class from the perspective of the rest of the system, and ensuring that this contract isn't broken by changes to the class.

2
  • Even with 100% code coverage you can still encounter non-obvious code paths that aren't tested, such as overflow conditions. Commented Nov 28, 2016 at 22:49
  • 3
    This sentence says it all: It is all about what is the expected behavior of your class from the perspective of the rest of the system. Unit testing and TDD made more sense to me when I stopped thinking about testing methods and instead tested behaviors.
    – Brandon
    Commented Nov 28, 2016 at 22:55
0

You building your test cases by changing input parameters or changing some public state of class under tests.
Every test case will execute different branch of your logic.
In your example exception will be thrown if somethingHappened - is this something a public state of the class or parameter given to the method? What is the case when if(somethingHappened) will return true?

Benefit of "black boxing" unit testing is that when you have covered tests you can easy refactor any private method without rewriting unit tests.

If exception case cannot be achieved through public api of the method/class, then how it can be achieved in the production?

0

Yes, you should indirectly test the code paths that go through private functions. I can prove this to you with a more extreme example:

private int privateFunction(int input)
{
    return [some int based on the input];
}

public int publicFunction(int input)
{
    return privateFunction(input);
}

Any meaningful test must test the logic of privateFunction in this case. The same is true in your more general case.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.