Serializable check is too harsh
See original GitHub issueHi, apologies if this has been discussed.
While upgrading my company’s Mockito to 1.10.19, I ran into a lot of this error:
org.mockito.exceptions.base.MockitoException:
You are using the setting 'withSettings().serializable()' however the type you are trying to mock 'OpportunityClient'
do not implement Serializable AND do not have a no-arg constructor.
This combination is requested, otherwise you will get an 'java.io.InvalidClassException' when the mock will be serialized
In summary, there are two cases triggering this error.
- The SubjectUnderTest accepts an object Foo and requires it to be Serializable. And yet Foo doesn’t implement Serializable nor has a default constructor.
- Foo itself is Serializable. But the test calls
when(foo.createBar()).thenReturn(bar)
, which behind the scene puts thebar
mock onto the state of foo, which requires bar itself to be Serializable. In production, bar isn’t required to be Serializable.
I feel case 1 is possibly reasonable (although it’s still kind of harsh. I’ll get to it in a bit).
Case 2 should not require Bar to be Serializable as it does today. In version 1.9.5, our tests worked around it by using mock(Bar.class, withSettings().serilizable()
. But in version 1.10.19, this workaround breaks if Bar has no default constructor.
Possibly the Serializable Proxy Pattern could be used to solve this nicely by stashing the “mock specifications” into a proxy object that can later on deserialize itself back into a mock Bar with the same number of when().thenReturn().
Now let me try to explain why I think even case 1 is too harsh.
Philosophically, I feel that it’s not Mockito’s job to ensure the mocked object works in real life. It’s a mock object after all. It isn’t expected to meet all required specification of the real object.
When in a test I say @Mock(serializable=true) Foo foo;
, I’m explicitly asking Mockito to “please pretend my object be Serializable. I don’t want to worry about the real object in this test”. Would the real object be serializable when I pass it into SubjectUnderTest? Well:
- There is nothing guaranteeing that production code passes Foo to SubjectUnderTest. It could likely pass a cousin class RealFoo2 that implements Serializable just fine (or not, but the test doesn’t help me with that). It needs to be tested, but IMHO not against a mock foo, especially when Mockito’s when().thenReturn() syntax adds non-existent Serializable requirement to the mock object.
- Given the proxy pattern, that Foo doesn’t look like a serializable class isn’t a guarantee that it can’t be. So by throwing this error, Mockito enforces stricter constraint than what’s technically necessary.
I agree that checking basic Serializable requirement in Mockito can catch some bugs. If case 2 can be fixed, it’s likely that case 1 would not have triggered so many false positives.
But with all our workarounds already added because of case 2 and the fair number of our tests triggering this error, the amount of work to fix all of them before we can upgrade Mockito looks daunting.
So, I guess my question is: can we do away with MockCreationValidator.validateSerializable() or make it an opt-in?
Sorry for the long message.
Issue Analytics
- State:
- Created 8 years ago
- Comments:7 (5 by maintainers)
Top GitHub Comments
Here’s a real example. The subject under test looks like this:
The testing framework actually tries to serialize SubjectUnderTest so FooFactory needs to be Serializable too.
The test using Mockito looks like this:
But when the framework serializes
subject
, we get an exception, because the line ofwhen(mockFactory.createFoo()).thenReturn(foo)
implicitly adds ‘foo’ as part ofmockFactory
state.To work around the problem, our tests had to make foo serializable too:
Again, production code doesn’t need Foo to be Serializable and it may not have a default constructor.
That’s status quo.
Now with v1.10.19, the above work-around breaks, because Foo doesn’t implement Serializable nor has a default constructor.
Back to square one, if I had the choice, I would strongly discourage the code that mocks either FooFactory or Foo. Instead, it should just be a plain old FooFactory subclass that returns Foo. With Java 8, the syntax would actually become more concise than the mockito syntax.
But I can’t go back and fix so many teams’ code. As it stands today, this is the biggest issue blocking the upgrade.
Hopefully I’ve made a clear case.
Thanks