Unit Testing – Are Mocks in Unit Tests Dangerous in Dynamic Languages?

dynamic programmingmockingPHPunit testing

I've started relying heavily on a mocking framework in php for my unit tests.

My concern is that with a dynamic language, there is no way of enforcing a return type. When mocking, you have to ensure that the return type of your mocked method is correct.

Take for example the following interface

interface MyInterface {

  /**
   * @return SomeObject
   */
   public function myMethod();

}

If we are unit testing a class which requires an instance of MyInterface, we would mock the myMethod function to return a known SomeObject.

My concern is what happens if we accidentally have our mocked method return a different type of object, e.g. SomeOtherObject, and SomeOtherObject and SomeObject are not the same duck type (don't have the same methods). We'll now have code that is relying on havingSomeOtherObject, when all implementations ofMyInterfacemyMethodwill returnSomeObject`.

For example, we could write the following production code;

$someOtherObject = $myInterfaceImp->myMethod();
$newObj = new NewObject($someOtherObject);

Hopefully the issue is obvious. We are passing what we think is SomeOtherObject into a constructor. Our tests will pass, but its very likely that in production we will get failures as myMethod() will actually be returning SomeObject. We then try passing this into the constructor, and get an error if there is a type check, or a non existent method is called.

Unless we write very detailed integration and acceptance tests using the real implementations (basically we'd have to ensure they covered all the testing we do an the unit level) we may not ever realise that we have an issue until we receive some runtime error in production.

Other than developer discipline, is there any way to mitigate the risk of incorrect mocks and implementations?

Best Answer

The answer to this question "are mocks in unit tests dangerous in dynamic languages?" is "Hell yes!"

The following article explains the dangers.

They describe a scenario where an interfaces method name changed. For example, changing a methods name from error() to errors(). If we are mocking the error() method in our tests, they will never turn red to alert us that we have a problem.

Instead, mocks should be used sparingly. Behaviours of the unit under test should be tested rather than testing implementation details, which happens as a consequence of using mocks in unit tests. Real classes should be used as dependencies, instead of an over reliance on mocks.