Do you know what’s worse that static methods or classes marked as final? I’ll tell you what’s worse: static methods that return final classes. That only provides private constructors.
Here I was, merrily testing my way through a piece of software that sends emails. According to the Java Mail documentation, you are supposed to first create an email session as follows:
Session mailSession = Session.getInstance(properties);
(it is worth noting that all what the getInstance() method does is call the private constructor for Session: new Session(props, null)).
Had Session been a more normal class, I could have mocked it like that:
Session mockSession = mock(Session.class);
and probably checked how it is being passed around like this:
verify(someService).startEmailSession(mockSession);
What follows is what I have to do instead.
The case of static methods
If getInstance() had been an instance method, I would have been able to mock it:
Session mockSessionProvider = mock(Session.class); Session mockSessionInstance = mock(Session.class); when(mockSessionProvider.getInstance()).thenReturn(mockSessionInstance);
Unfortunately, a static method cannot easily be mocked with Mockito (other mocking frameworks support that, but I believe they make the tests too obscure). So, first step: create a wrapper class just for the builder method.
public class SessionProvider { public Session getInstance(Properties properties) { return Session.getInstance(properties); } }
The case of final classes
So far, so (relatively) good. However, since Session is a final class, the usual mocking mecanism under Mockito does not work:
SessionProvider mockSessionProvider = mock(SessionProvider.class); Session mockSessionInstance = mock(Session.class); // fails because Session is final when(mockSessionProvider.getInstance()).thenReturn(mockSessionInstance);
The case of private constructors
Another option would have been to instantiate the session instance, but that fails too, since the constructor is private:
SessionProvider mockSessionProvider = mock(SessionProvider.class); Session sessionInstance = new Session(properties, null); // fails because constructor is private when(mockSessionProvider.getInstance()).thenReturn(sessionInstance);
A third way might have been to call the original Session.getInstance() to create the test object:
SessionProvider mockSessionProvider = mock(SessionProvider.class); Session sessionInstance = Session.getInstance(properties); when(mockSessionProvider.getInstance()).thenReturn(sessionInstance);
That would work, but that also opens a whole new can of worms. Session does not implement the equals() method, so if I want to check the value of the Session instance passed around, I must either:
- compare with the pointer to the sessionInstance — the issue is that there is no way to check that the session has not been changed (any call to a setter method on the session instance will have no effect on the comparison of pointers)
- implement a generic deep equal matcher for Mockito — something we did at my company, but that I am reluctant to do myself
- use an argument captor to catch the instance passed and test — which produces a fair amount of awkward lines of test
- create a custom argument matcher — which hides the comparison in a separate class or method
- yet another is to wrap the instance of Session in yet another class, such as SessionInstance — which would require us to also wrap any service that takes an instance of Session
So we also need a wrapper for the Session instance:
public class SessionInstance { private final Session session; public SessionInstance(Session session) { this.session = session; } public Session getSession() { return session; } }
Which forces us to change the SessionProvider:
public class SessionProvider { public SessionInstance getInstance(Properties properties) { return new SessionInstance(Session.getInstance(properties)); } }
At last, we can mock this way:
SessionProvider mockSessionProvider = mock(SessionProvider.class); SessionInstance mockSessionInstance = mock(SessionInstance.class); when(mockSessionProvider.getInstance()).thenReturn(mockSessionInstance);
Done? Hardly. All other, possibly mockable, classes from third-parties that rely on instances of Session must now also by wrapper. For example, a third-party JavaMailTransport that requires an instance of Session now needs its own wrapper if I want to test it. Sigh…
Hi Eric,
I’ve used some of those (static and final) in a highly secure RMI environment, so I suppose they have their place. Anyhow, this is not really the point.
I used to participate in Algodeal and see it closed down. How about a post about that – I’d love some more info? What was your experience there, why did it close, what happens now….
Regards
MG
@MG Hi! Yes, I remember you from Algodeal, of course!
Yes, I’d like to write a post on the ending of Algodeal — I am personally fairly disappointed about it. I think it is better to wait a bit more, though. Things such as ownership of code are still being sorted out, but the big think is that it is still to vivid in memory. I don’t want to end up blaming the wrong people and some cooling off will help ;-).
Thanks – sorry for highjacking this, was the easiest way for me to get in touch.
When David left I got suspicious and deleted all my work there, but kept on monitoring it and hoped for some miracle.
Such a pity, I got the impression you guys were doing cutting edge stuff. Still even if all went well it was a risky venture I suppose. I look forward to that post.
Regards
1 word .. jmockit .. problem solved ..