views:

210

answers:

5

Hi,

I would like to know what's the best approach to test the method "pushEvent()" in the following class with a jUnit test. My problem is, that the private method "callWebsite()" always requires a connection to the network. How can I avoid this requirement or refactor my class that I can test it without a connection to the network?

class MyClass {

    public String pushEvent (Event event) {
        //do something here
        String url = constructURL (event); //construct the website url
        String response = callWebsite (url);

        return response;
    }

    private String callWebsite (String url) {
        try {
            URL requestURL = new URL (url);
            HttpURLConnection connection = null;
            connection = (HttpURLConnection) requestURL.openConnection ();


            String responseMessage = responseParser.getResponseMessage (connection);
            return responseMessage;
        } catch (MalformedURLException e) {
            e.printStackTrace ();
            return e.getMessage ();
        } catch (IOException e) {
            e.printStackTrace ();
            return e.getMessage ();
        }
    }

}
+9  A: 

Mocking

You'll need a test double to allow isolated, easy, unit testing. The following is non tested, but demonstrates the idea. The use of Dependency Injection will allow you to inject at test time, a test version of your HttpURLConnection.

public class MyClass() 
{
   private IHttpURLConnection httpUrlConnection;   

   public MyClass(IHttpURLConnection httpUrlConnection)
   {
       this.httpUrlConnection = httpUrlConnection;
   }

   public String pushEvent(Event event) 
   {
       String url = constructURL(event);
       String response = callWebsite(url);
       return response;
  }
}

Then you create test double (Mock Object/Stub) to be the stand in for the concrete instance.

class TestHttpURLConnection : IHttpURLConnection { /* Methods */ }

You'll also construct a concrete version, for your production code to use.

class MyHttpURLConnection : IHttpURLConnection { /* Methods */ }

Using your test class (an adapter) you are able to specifiy what should happen during your test. A mocking framework will enable you to do this with less code, or you can manually wire this up. The end result of this for your test is that you'll set your expectations for your test, for example, in this case you may set OpenConnection to return a true boolean (This is just an example by the way). Your test will then assert that when this value is true, the return value of your PushEvent method matches some expected result. I've not touched Java properly for a while, but here are some recommended mocking frameworks as specified by StackOverflow memebers.

Finglas
I was just about to type something very similar. I think he needs to decouple his class from the url connection like you mentioned. then his unit test will test the interaction with a mock urlconnection. He also needs an integration test to test the integration with a concrete instance of the url connection.
Athens
I should mention mocking is an overloaded term. Essentially it all boils down to using a fake object to enable isolated testing, check the other mocking questions here on SO for a broader overview. If someone tells you to use mocking, as I have, you can do so with a fake/stub/mock/hand rolled object.
Finglas
+1, this is a more general, applicable solution than the accepted answer.
Grundlefleck
@Grundlefleck I agree, but at the same time the accepted answer is a perfectly valid solution. In fact, I'd recommend test extensions over mocking frameworks to begin when unit testing. Once test extensions become painful/and or tedious - it's time to move to a mocking framework.
Finglas
+4  A: 

Possible solution: You can extend this class, override callWebsite (you have to make it protected for this purpose) - and the override method write some stub method implementation.

duduamar
A: 

Create an abstract class WebsiteCaller which would be a parent of ConcreteWebsiteCaller and WebsiteCallerStub.

This class should have one method callWebsite (String url). Move your callWebsite method from MyClass to ConcreteWebsiteCaller. And MyClass will look like:

class MyClass {

    private WebsiteCaller caller;

    public MyClass (WebsiteCaller caller) {
        this.caller = caller;
    }

    public String pushEvent (Event event) {
        //do something here
        String url = constructURL (event); //construct the website url
        String response = caller.callWebsite (url);

        return response;
    }
}

and implement method callWebsite in your WebsiteCallerStub in some way appropriate for testing.

Then in your unit test do something like this:

@Test
public void testPushEvent() {
   MyClass mc = new MyClass (new WebsiteCallerStub());
   mc.pushEvent (new Event(...));
}
Roman
I think what you are demonstrating is an integration test as your test relies on an external resource. A better unit test would inject a mock/stub websitecaller interface. your integration test can test the websitecaller class exclusively as well as the websitecaller injected into Mycalss
Athens
+2  A: 

Approaching things from a slightly different angle...

I'd worry less about testing this specific class. The code in it is extremely simple and, while a functional test to make sure it's working with a connection would be helpful, a unit level test "may" not be necessary.

Instead, I'd focus on testing the methods it calls that appear to actually do something. Specifically...

I'd test constructURL method from this line:

String url = constructURL (event);

making sure that it can construct a URL properly from different Events, and throws Exceptions when it should (possibly on an invalid Event or null).

And I'd test the method from the following line:

String responseMessage = responseParser.getResponseMessage (connection);

Possibly pulling out any "get information out of the connection" logic into one proc, and leaving only "parse said information" in the original one:

String responseMessage = responseParser.getResponseMessage(responseParser.getResponseFromConnection(connection));

or something along those lines.

The idea being to put any "must deal with external data sources" code in one method, and any code logic in separate methods that can be easily tested.

RHSeeger
+1  A: 

As an alternative to Finglas's helpful answer with respect to mocking, consider a stubbed approach where we override the functionality of callWebsite(). This works quite well in the case where we aren't so interested in the logic of callWebsite as that of the other logic called within pushEvent(). One important thing to check is that callWebsite is calledwith the correct URL. So, first change is to the method signature of callWebsite() to become:

protected String callWebsite(String url){...}

Now we create a stubbed class like this:

class MyClassStub extends MyClass {
    private String callWebsiteUrl;
    public static final String RESPONSE = "Response from callWebsite()";

    protected String callWebsite(String url) {
        //don't actually call the website, just hold onto the url it was going to use
        callWebsiteUrl = url;
        return RESPONSE;
    }
    public String getCallWebsiteUrl() { 
        return callWebsiteUrl; 
    }
}

And finally in our JUnit test:

public class MyClassTest extends TestCase {
    private MyClass classUnderTest;
    protected void setUp() {
        classUnderTest = new MyClassStub();
    }
    public void testPushEvent() { //could do with a more descriptive name
        //create some Event object 'event' here
        String response = classUnderTest.pushEvent(event);
        //possibly have other assertions here
        assertEquals("http://some.url", 
                     (MyClassStub)classUnderTest.getCallWebsiteUrl());
        //finally, check that the response from the callWebsite() hasn't been 
        //modified before being returned back from pushEvent()
        assertEquals(MyClassStub.RESPONSE, response);
    }
 }
Chris Knight