How do I mock objects that I can't instantiate in my tests?

Sven picture Sven · Jan 26, 2011 · Viewed 7.1k times · Source

I'm using EasyMock to mock objects in my tests. But how do I mock objects that are created somewhere else in my code? Look at the following psudo code. I want to mock WebService#getPersonById, how do I do that?

public class Person {
  public Person find(int id) {
    WebService ws = new WebService();
    return ws.getPersonById(id);
  }
}

public class PersonTest {
  testFind() {
    // How do I mock WebService#getPersonById here?
  }
}

Answer

hvgotcodes picture hvgotcodes · Jan 26, 2011

Mocking typically works well if you use inversion of control and dependency injection to wire up your services. So your person should look like

public class Person() {
  WebService ws = null;

  // or use setters instead of constructor injection
  Persion(WebService ws) {
     this.ws = ws;
  }
  public Person find(int id) {
    return ws.getPersonById(id);
  }
}

hopefully it is clear that with this change, you can now create a mock and mock control for WebService and just plug it in in your test, because when you create the Person to test, you can pass in the mock to the constructor (or setter if you go that route).

In your real environment, the IoC container will inject the real web service in.

Now, if you don't want to deal with all this IoC stuff, what you need to do is decouple your webservice from your Person (which should be call PersonService or something, not just Person, which denotes entity). In other words, the way the code is written you can only use one type of WebService. You need to make it so the Person just needs some type of WebService, not the specific one you have hard-coded in.

Finally, in the code as written, WebService is a class, not an interface. The WebService should be an interface, and you should put in some sort of implementation. EasyMock works well with interfaces; it might be able to mock concrete classes (been a while since I actually used it), but as a design principle you should specify the required interface, not the concrete class.