Worse than static methods or final classes?

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…

About Eric Lefevre-Ardant

Independent technical consultant.
This entry was posted in java, tdd. Bookmark the permalink.

4 Responses to Worse than static methods or final classes?

  1. MG says:

    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

  2. @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 ;-).

  3. MG says:

    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

  4. Anonymous says:

    1 word .. jmockit .. problem solved ..

Comments are closed.