Last Updated: February 25, 2016
·
2.741K
· juliomistral

PowerMock - Completing your unit testing toolkit

Let's face it: Mocking/Stubbing are so integral to proper unit testing, that you really can't unit test without it. But the standard libraries - EasyMock, Mockito, JMock, etc. - don't support mocking/stubbing the following uses cases out of the box:

  • Static method calls (MyStaticClass.someStaticMethod)
  • Constructors (new MyClass())
  • Verify that a private method was called

Now, when ever you look this up you'll see a lot of responses along the lines of "if you need to do this, you're design is wrong...and you're a jerk!". Well, most time this is true, but the fact of the matter is you can have a really well design application BUT you are still using static methods and constructors. You can't use a factory in EVERY case, and besides: how would you properly unit test a factory unless you can verify what it constructs AND how it constructs it.

So one of the best things I ever found for unit testing in Java is PowerMock. By using it in conjunction with EasyMock or Mockito, you open up your mocking/stubbing toolset to include the aforementioned cases. Here's a unit test I wrote as part of a pet project I'm starting up, a web resource crawler and reporter called charlotte.

Take a look, learn it AND use it! May the unit tests be with you...


package com.hcisf.charlotte.loader;


import com.hcisf.PowerMockWithSpecTestNameRunner;

import com.hcisf.charlotte.domain.Resource;
import com.hcisf.charlotte.domain.ResourceStatus;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import org.powermock.api.mockito.PowerMockito;
import static org.mockito.Mockito.*;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;


@RunWith(PowerMockWithSpecTestNameRunner.class)
@PrepareForTest({Jsoup.class, JsoupHttpLoader.class})
public class JsoupHttpLoaderTest  {
    private static final int TIMEOUT = 1000;
    private static final String RESOURCE_URL = "http://google.com";

    JsoupHttpLoader loader;
    Resource resource;
    Document document;
    Elements elements;

    @Before
    public void setup() throws Exception {
        PowerMockito.mockStatic(Jsoup.class);

        resource = new Resource(RESOURCE_URL);
        document = mock(Document.class);
        elements = new Elements();
        doReturn(elements).when(document).select(anyString());

        when(
            Jsoup.parse(any(URL.class), anyInt())
        ).thenReturn(
            document
        );

        loader = new JsoupHttpLoader(TIMEOUT);
    }

    @Test
    public void shouldAttemptToLoadTheContentAtTheProvidedResourceLocation() throws Exception {
        // When the loader populates the resource
        loader.populateResource(resource);

        // Then parsing library is asked to parse the content at the resource location
        PowerMockito.verifyStatic();
        Jsoup.parse(any(URL.class), eq(TIMEOUT));
    }

    @Test
    public void shouldReturnAResourceSetToParsedIfTheLoadedContentIsParsedSuccessfully() throws Exception {
        // When the loader loads the resource URL
        Resource output;
        output = loader.loadResource(RESOURCE_URL);

        // Then the returned resource is set to parsed
        assertNotNull(output);
        assertEquals(ResourceStatus.PARSED, output.status);
    }

    @Test
    public void shouldReturnAResourceSetToInvalidIfLocationIsMalformed() throws Exception {
        // Given the provided resource location is malformed
        PowerMockito.whenNew(URL.class)
                    .withArguments(RESOURCE_URL)
                    .thenThrow(new MalformedURLException());

        // When the loader loads the resource URL
        Resource output;
        output = loader.loadResource(RESOURCE_URL);

        // Then the returned resource is set to invalid
        assertNotNull(output);
        assertEquals(ResourceStatus.INVALID, output.status);
    }

    @Test
    public void shouldReturnAResourceSetToUnavailableIfCannotConnectToLocation() throws Exception {
        // Given the provided resource location is unavailable
        PowerMockito.whenNew(URL.class)
                    .withArguments(RESOURCE_URL)
                    .thenThrow(new IOException());

        // When the loader loads the resource URL
        Resource output;
        output = loader.loadResource(RESOURCE_URL);

        // Then the returned resource is set to unavailable
        assertNotNull(output);
        assertEquals(ResourceStatus.UNAVAILABLE, output.status);
    }

    @Test
    public void shouldCreateAChildResourceForEveryLinkFoundInTheParsedDocument() throws Exception {
        // Given the parsed document contains a link in it
        Element element = mock(Element.class);
        when(
            element.attr(anyString())
        ).thenReturn(
            RESOURCE_URL
        );
        elements.add(element);

        // When the loader loads the resource URL
        Resource output;
        output = loader.loadResource(RESOURCE_URL);

        // Then the returned resource has 1 child
        assertNotNull(output.children);
        assert output.children.size() == 1;
        Resource child = output.children.get(0);

        // Add the child is an empty resource with it's location set
        assertEquals(ResourceStatus.EMPTY, child.status);
        assertEquals(RESOURCE_URL, child.location);
    }
}