9

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?
  }
}
Sven
  • 913
  • 8
  • 16

5 Answers5

9

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.

hvgotcodes
  • 118,147
  • 33
  • 203
  • 236
  • +1 - this is almost exactly what I added in my (now-deleted) answer. – Andrzej Doyle Jan 26 '11 at 16:58
  • 1
    I guess one could say that there's a code smell when you can't mock an object then? :-) I guess I have to rethink my implementation. Many thanks for your awesome answer and example. – Sven Jan 26 '11 at 17:09
  • But what if my find method is static? – Sven Jan 26 '11 at 17:34
  • @sven, find shouldn't really be static. In the case that it is, you would need a static resource for the webservice. You could still set a mock on it, but it would be uglier – hvgotcodes Jan 26 '11 at 18:03
  • @andrzej, yeah i noticed you deleted your post. Thanx, that doesn't happen enough when someone beats another to the right answer. Im gonna find some good answers of yours and give you some upvotes for your 'sportsmanship'. – hvgotcodes Jan 26 '11 at 18:04
  • @jwir3, thanx. this answer is write in my wheelhouse so i could bang it out fast ;) – hvgotcodes Jan 26 '11 at 18:04
  • @sven, yes code smell is one way to say it. the smell is indicative of tight coupling, in this case. – hvgotcodes Jan 26 '11 at 18:05
  • Agreed, I would move the `find` method to some other class, probably `PersonService` as suggested (there could then be a single finder method for `Person` objects); probably the `Person` class is better off without a web service dependency. However, I would still have a concrete (and `final`) `PersonService` class, *assuming* there isn't a requirement to load `Person` objects from multiple sources *and* to select the implementation of the service at runtime. Creating separate interfaces and using a DI framework shouldn't be done just because we can; *simplicity* is the best design principle. – Rogério Feb 14 '11 at 13:52
3

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.

Rogério
  • 16,171
  • 2
  • 50
  • 63
1

I think you're missing a much bigger problem. The difficulty in testing is trying to tell you something, that having a Person object (part of the domain) that also uses a remote service to find further instances of itself (part of the system) is mixing up concerns. Separate the getting of Persons out of the Person object and you'll end up with cleaner, more portable code.

Don't confuse immediate convenience (I have a Person object in my hand, so I'll use that for getting more) with maintainability.

Steve Freeman
  • 2,707
  • 19
  • 14
0

You can try using PowerMock whenNew method

https://github.com/jayway/powermock/wiki/MockitoUsage#how-to-mock-construction-of-new-objects

More examples here - http://www.programcreek.com/java-api-examples/index.php?api=org.powermock.api.mockito.PowerMockito

your code might look like -

whenNew(WebService.class).withAnyArguments().thenReturn(yourMockInstanceToWebServiceClass);

Madhav
  • 721
  • 8
  • 10
0

First of you need to make ws a mock, usually by injecting it.

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

Then you can slip it in and use EasyMock.expect to setup the return

public class PersonTest() {
  testFind() {
    WebService mock = EasyMock.createMock(WebService.class);
    Person p = new Persion() {
      protected WebService createWebService() {
        return mock;
      }
    }
    EasyMock.expect(mock.getPersonById()).andReturn(dummyValue);
    //Test code
  }
}

You'll also need a PersonImpl to have the real create method.

sblundy
  • 60,628
  • 22
  • 121
  • 123