If we supply a “real” controller for testing another component, then, strictly speaking, we run an integration test, not a unit test. This is not necessarily bad, but consider the following points:
The cost of creating a controller
If the controller is a heavy object with significant construction costs, then each unit test will bear this cost. As the number of unit tests grows in quantity, this cost may begin to dominate the total test run time. It is always desirable that the battery life is minimal to ensure a quick turn after changing the code.
Controller dependencies
If the controller is a complex object, it may have its own dependencies that must be created to create the controller itself. For example, you might need access to a database file or a configuration file. Now, not only do you need to initialize the controller, but also those components. As the application develops over time, the controller may require more and more dependencies, just making this problem even worse over time.
Controller status
If the controller carries any state, performing a unit test can change that state. This, in turn, can change the behavior of subsequent unit tests. Such changes can lead to the apparently non-deterministic behavior of single tests, introducing the ability to mask errors. The fix for this problem is to re-create the controller for each test, which may be impractical if the creation is expensive (as mentioned above).
Combinatorial task
The number of combinations of possible inputs into the composite system of the device under test and the controller object can be much larger than the number of combinations for one device. This number may be too large for testing. Testing a block in isolation with a plug or a mock object instead of a controller makes it easier to control the number of combinations.
Object of god
If the controller is conveniently accessible for all components in each unit test, it will be a great temptation to turn the controller into a God Object that knows everything about every component of the system. Worse, these components can begin to interact with each other through this deity object. The end result is that the separation between application components begins to break down, and the system begins to become monolithic.
Technical debt
Even if the controller is inactive today and cheap to create an instance, this can change as the application develops. If this day comes after we have written a large number of unit tests, we may encounter a lot of refactoring of all these tests. Moreover, actual system code may also need refactoring to replace all references to controllers with lighter interfaces. There is a risk that the cost of refactoring is significant - perhaps even too high to behold, as a result of which the system "gets stuck" in an undesirable form.
Recommendation
To avoid these errors now and in the future, my recommendation is to not deliver the real controller to unit tests.
A full controller will probably be difficult to drown or mock you. This will cause (desired) pressure to express component dependencies as a “thin”, focused interface instead of the “thick”, “kitchen sink” that may be present in the controller. Why is this desirable? This is desirable because this practice promotes a better separation of problems between system components, which provides architectural benefits far beyond the unit test database.
For a lot of good practical tips on how to achieve separation of problems and generally write test code, see the Misko Hevery guide and.