Testing pure function on union type which delegates to other pure functions

The best solution would be just testing for foo.

fnForString and fnForNumber are an implementation detail that you may change in the future without necessarily changing the behaviour of foo. If that happens your tests may break with no reason, this kind of problem makes your test too expansive and useless.

Your interface just needs foo, just test for it.

If you have to test for fnForString and fnForNumber keep this kind of test apart from your public interface tests.

This is my interpretation of the following principle stated by Kent Beck

Programmer tests should be sensitive to behaviour changes and insensitive to structure changes. If the program’s behavior is stable from an observer’s perspective, no tests should change.

Short answer: the specification of a function determines the manner in which it should be tested.

Long answer:

Testing = using a set of test cases (hopefully representative of all cases that may be encountered) to verify that an implementation meets its specification.

In the example foo is stated without specification, so one should go about testing foo by doing nothing at all (or at most some silly tests to verify the implicit requirement that "foo terminates in one way or another").

If the specification is something operational like "this function returns the result of applying args to either fnForString or fnForNumber according to the type of args" then mocking the delegates (option 2) is the way to go. No matter what happens to fnForString/Number, foo remains in accordance with its specification.

If the specification does not depend on fnForType in such a manner then re-using the tests for fnFortype (option 1) is the way to go (assuming those tests are good).

Note that operational specifications remove much of the usual freedom to replace one implementation by another (one that is more elegant/readable/efficient/etc). They should only be used after careful consideration.