Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?
  }
}
like image 523
Sven Avatar asked Jan 26 '11 16:01

Sven


People also ask

What objects can be mocked?

Mock objects are simulated objects that mimic the behavior of the real ones. Typically you write a mock object if: The real object is too complex to incorporate it in a unit testing (For example a networking communication, you can have a mock object that simulate been the other peer)

How can specify mock behavior of an object?

When you create a mock, you create an associated behavior object that controls mock behavior. Use this object to define mock method and property behavior (stub). For more information on creating a mock, see Create Mock Object.


2 Answers

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.

like image 173
hvgotcodes Avatar answered Nov 15 '22 18:11

hvgotcodes


There is no way to do it with EasyMock (or most other mocking APIs). With JMockit, on the other hand, such a test would be very simple and elegant:

public class PersonTest
{
    @Test
    public testFind(@Mocked final WebService ws) {
        final int id = 123;

        new NonStrictExpectations() {{
            ws.getPersonById(id); result = new Person(id);
        }};

        Person personFound = new Person().find(id);

        assertEquals(id, personFound.getId());
    }
}

So, whenever we run into a situation where a unit test cannot be written at first, we can't automatically conclude that the code under test is untestable and needs to be refactored. Sometimes it will be the case, but certainly not always. Maybe the problem is not in the code under test, but in the limitations of a particular mocking tool that is being used.

like image 35
Rogério Avatar answered Nov 15 '22 20:11

Rogério