On Thu, Aug 29, 2019 at 1:02 AM Krinkle krinklemail@gmail.com wrote:
What did you want to assert in this test?
In a proper unit test, I want to completely replace all non-value classes with mocks, so that they don't call the actual class' code. This way I can test the class under test without making assumptions about other classes' behavior.
This is not possible at all if any method is declared final. As soon as the class under test calls a final method, you cannot mock the object. This is without any use of expects() or with() -- even just method()->willReturn().
I find there is sometimes a tendency for a test to needlessly duplicate the source code by being too strict on expecting exactly which method is called to the point that it becomes nothing but a more verbose version of the source code; always requiring a change to both.
Personally, I prefer a style of testing where it providers a simpler view of the code. More like a manual of how it should be used, and what observable outcome it should produce.
The idea of good unit tests is to allow refactoring without having to worry too much that you're accidentally changing observable behavior in an undesired way. Ideally, then, any observable behavior should be tested. Changes in source code that don't affect observable behavior will never necessitate a change to a test, as long as the test doesn't cheat with TestingAccessWrapper or such.
This includes tests for corner cases where the original behavior was never considered or intended. This is obviously less important to test than basic functionality, but in practice, callers often accidentally depend on all sorts of incidental implementation details. Thus ideally, they should be tested. If the test needs to be updated, that means that some caller somewhere might break, and that should be taken into consideration.
On Thu, Aug 29, 2019 at 1:12 AM Aaron Schulz aschulz4587@gmail.com wrote:
Interfaces will not work well for protected methods that need to be overriden and called by an abstract base class.
If you have an interface that the class implements, then it's possible to mock the interface instead of the class, and the final method problem goes away. Of course, your "final" is then not very useful if someone implements the interface instead of extending the class, but that shouldn't happen if your base class has a lot of code that subclasses need.
On Thu, Aug 29, 2019 at 10:37 AM Daniel Kinzler dkinzler@wikimedia.org wrote:
Narrow interfaces help with that. If we had for instance a cache interface that defined just the get() and set() methods, and that's all the code needs, then we can just provide a mock for that interface, and we wouldn't have to worry about WANObjectCache or its final methods at all.
Any interface would solve the problem, even if it was just a copy of all the public methods of WANObjectCache. That would be inelegant, but another possible solution if we want to keep using final.