16

I need to test handleIn() method using Mockito.

However the code need to call this legacy code Util.getContextPDO which is a static method.

Note that in testing environment this Util.getContextPDO is always returns Exception, and I intend to bypass this Util.getContextPDO() by always return a dummy IPDO.

public class MyClass {
  public IPDO getIPDO() 
  {
    return Util.getContextPDO(); // note that Util.getContextPDO() is a static, not mockable.
  }

  public String handleIn(Object input) throws Throwable 
  {
    String result = "";
    IPDO pdo = getIPDO();

    // some important business logic.

    return result;
  } 
}

Initially I thought this achieveable by using spy() of the class "MyClass", so I can mock the return value of getIPDO(). Below is my initial effort using spy ()

@Test
public void testHandleIn() throws Exception
{
    IPDO pdo = new PDODummy();


    MyClass handler = new MyClass ();
    MyClass handler2 = spy(handler);

    when(handler2.getIPDO()).thenReturn(pdo);
    PDOUtil.setPDO(pdo, LogicalFieldEnum.P_TX_CTGY, "test123");
    IPDO pdoNew = handler2.getIPDO();

    Assert.assertEquals("test123,(PDOUtil.getValueAsString(pdoNew, LogicalFieldEnum.P_TX_CTGY)));

}

However the when(handler2.getIPDO()).thenReturn(pdo); is throwing the Exception that I want to avoid ( because handler2.getIPDO() ) seems to call the real method.

Any idea on how to test this part of code?

bric3
  • 40,072
  • 9
  • 91
  • 111
Rudy
  • 7,008
  • 12
  • 50
  • 85
  • We are using **PowerMock** together with **Mockito** to wotk with legacy. This turns so easy so we made rule to avoid **PowerMock** to fight with legacy more effective – Eugen Martynov Dec 28 '12 at 14:23
  • 1
    The "only" issue with PowerMock is that you are forced to use its own test runner, which is not always an option (when writing Android Robolectric tests, for instance, which require their own robolectric runner) – David Santiago Turiño Jul 25 '13 at 09:31

3 Answers3

13

A good technique for getting rid of static calls on 3rd party API is hiding the static call behind an interface.

Let's say you make this interface :

interface IPDOFacade {

    IPDO getContextPDO();
}

and have a default implementation that simply calls the static method on the 3rd party API :

class IPDOFacadeImpl implements IPDOFacade {

    @Override
    public IPDO getContextPDO() {
        return Util.getContextPDO();
    }
}

Then it is simply a matter of injecting a dependency on the interface into MyClass and using the interface, rather than the 3rd party API directly :

public class MyClass {

    private final IPDOFacade ipdoFacade;

    public MyClass(IPDOFacade ipdoFacade) {
        this.ipdoFacade = ipdoFacade;
    }

    private IPDO getIPDO() {
        return ipdoFacade.getContextPDO();
    }

    public String handleIn(Object input) throws Throwable
    {
        String result = "";
        IPDO pdo = getIPDO();

        someImportantBusinessLogic(pdo);

        return result;
    }
    
    ...

}

In your unit test, you can then easily mock your own interface, stub it any way you like and inject it into the unit under test.

This

  • avoids the need to make private methods package private.
  • makes your tests more readable by avoiding partial mocking.
  • applies inversion of control.
  • decouples your application from a specific 3rd party library.
bowmore
  • 10,842
  • 1
  • 35
  • 43
  • Now, how do you write a unit test for IPDOFacadeImpl.getContextPDO() ? – aquacode Dec 11 '13 at 13:26
  • You don't, you write an integration test for it. – bowmore Dec 11 '13 at 14:30
  • Good answer. This is more code/files, but feels right. It allows you to avoid spies (which Mockito says to avoid), allows you to avoid warnings about invoking static methods on instance objects, and complies to IoC. – Marquee Mar 27 '14 at 15:38
  • in MyClass before `someImportantBusinessLogic(pdo);` it should be `IPDO pdo = ipdoFacade.getIPDO();` right?? @Marquee @bowmore @aquacode – hushed_voice Apr 22 '22 at 04:28
  • 1
    @hushed_voice I intended the `getDPO()` method from the OP to be rewritten to use the facade but hadn't included that, I've corrected that. But you could indeed also use the facade method directly. – bowmore Apr 23 '22 at 15:48
12

Changed my testing to :

@Test
public void testHandleIn() throws Exception
{
  IPDO pdo = new PDODummy();


  MyClass handler = new MyClass ();
  MyClass handler2 = spy(handler);

  doReturn(pdo ).when( handler2 ).getIPDO();
  PDOUtil.setPDO(pdo, LogicalFieldEnum.P_TX_CTGY, "test123");
  IPDO pdoNew = handler2.getIPDO();

  Assert.assertEquals("test123,(PDOUtil.getValueAsString(pdoNew, LogicalFieldEnum.P_TX_CTGY)));

}

Solved after reading Effective Mockito.

Rudy
  • 7,008
  • 12
  • 50
  • 85
  • 2
    The "problem" with spies, that the actual method is called during stubbing. The only way to avoid this is to use the doXXXX.when(mock).somemethod(anyParam()) form. – Gábor Lipták Dec 28 '12 at 09:33
1
when(handler2.getIPDO()).thenReturn(pdo);

Will actually call the method and then return pdo regardless.

Whereas:

doReturn(pdo).when(handler2).getIPDO();

Will return pdo without calling the getIPDO() method.

Aequitas
  • 2,205
  • 1
  • 25
  • 51