final
methods absolutely are not bad practice in general: They convey a specific piece of information about the behavior and semantics of a method—namely, that anyone calling that method will get exactly that implementation. That's exactly the property you're trying to subvert through mocking, because you're overriding the final implementation through a (dynamically-generated) subclass to perform the stubbed behavior instead.
To that end, if you have an implementation you control that you're trying to replace in tests with a stub, it probably shouldn't be final
, because you're treating it as a non-final
method yourself in your test. Your test is another user of your component, and one you can design for accordingly.
In implementations you don't control, such as third-party libraries, PowerMock is a generally-accepted solution for mocking constructors as well as private
, static
and final
methods, potentially in final
classes. PowerMock does come with some hazards, though:
- There is additional complexity of mocking: PowerMock works through a special classloader that rewrites the classes themselves, rather than through Java's OOP method dispatch, so you have to explicitly list the classes calling the
final
andstatic
methods you're stubbing so Powermock can replace those calls. - There is additional risk in mocking classes you don't control, if you do so: You may be left in a bad position if the API or implementation changes in an incompatible way.
- By violating the semantics of the
final
modifier, it may make your code harder to read—you're declaring the method to have one well-defined predictable behavior and then working around your own declaration.
In terms of "best practices", the real best practice is the OOP tenet jgitter alludes to above: "Program to interfaces, not implementations." By relying on explicit interfaces:
- ...you make extremely clear the contract of the object you're interacting with.
- ...you are free to stub and verify methods without involving implementation details like
final
methods, methods not visible to the test, or methods in superclasses not visible to the test. Final methods are particularly insidious because Mockito can't warn you about them. - ...you make it easy to change out implementations, including improved future implementations, Mockito-generated mocks as you have here, or full fake implementations for testing.
That said, the ease with which Mockito stubs concrete classes makes it a tempting option, but be aware that the simplest semantically-correct answer may be to just remove final
from a particular method that needs stubbing.