Tuesday, May 3, 2011

Strategies to mock a webservice

I'm implementing a client consuming a webservice. I want to reduce dependencies and decided to mock the webservice.
I use mockito, it has the advantage vs. EasyMock to be able to mock classes, not just interfaces. But that's not the point.

In my test, I've got this code:

// Mock the required objects
Document mDocument = mock(Document.class);
Element mRootElement = mock(Element.class);
Element mGeonameElement = mock(Element.class);
Element mLatElement = mock(Element.class);
Element mLonElement = mock(Element.class);

// record their behavior
when(mDocument.getRootElement()).thenReturn(mRootElement);
when(mRootElement.getChild("geoname")).thenReturn(mGeonameElement);
when(mGeonameElement.getChild("lat")).thenReturn(mLatElement);
when(mGeonameElement.getChild("lon")).thenReturn(mLonElement);
// A_LOCATION_BEAN is a simple pojo for lat & lon, don't care about it!
when(mLatElement.getText()).thenReturn(
    Float.toString(A_LOCATION_BEAN.getLat()));
when(mLonElement.getText()).thenReturn(
    Float.toString(A_LOCATION_BEAN.getLon()));

// let it work!
GeoLocationFetcher geoLocationFetcher = GeoLocationFetcher
    .getInstance();
LocationBean locationBean = geoLocationFetcher
    .extractGeoLocationFromXml(mDocument);

// verify their behavior
verify(mDocument).getRootElement();
verify(mRootElement).getChild("geoname");
verify(mGeonameElement).getChild("lat");
verify(mGeonameElement).getChild("lon");
verify(mLatElement).getText();
verify(mLonElement).getText();

assertEquals(A_LOCATION_BEAN, locationBean);

What my code shows is that I "micro-test" the consuming object. It's like I would implement my productive code in my test. An example for the result xml is London on GeoNames. In my opinion, it's far too granular.

But how can I mock a webservice without giving everystep? Should I let the mock object just return a XML file?

It's not about the code, but the approach.

I'm using JUnit 4.x and Mockito 1.7

From stackoverflow
  • you really want to be mocking the results returned from the webservice to the code that will be using the result. In your example code above you seem to be mocking mDocument but you really want to pass in an instance of mDocument that has been returned from a mocked instance of your webservice and assert that the locationBean returned from the geoLocationFetcher matches the value of A_LOCATION_BEAN.

    furtelwart : Thanks, I got your point. So how would you "mock a webservice"?
    lomaxx : usually I'd declare an interface and create a webservice wrapper that implements the interface and instantiates the webservice
    furtelwart : So you just check the result, your webservice consumer returns?
  • I think the real problem here is that you have a singleton that calls and creates the web service so it is difficult to insert a mock one.

    You may have to add (possibly package level) access to the singleton class. For example if the constructor looks something like

    private GeoLocationFactory(WebService service) {
       ...
    }
    

    you can make the constructor package level and just create one with a mocked web service.

    Alternatively you can set the webservice by adding a setter method, although I don't like mutable Singletons. Also in that case you have to remember to unset the webservice afterwards.

    If the webservice is created in a method you might have to make the GeoLocationFactory extensible to substitute the mock service.

    You may also look into remove the singleton itself. There are articles online and probably here on how to do that.

0 comments:

Post a Comment