5

Assuming I have the following classes

public class StaticClass {

    public static void staticMethod() throws SomeException {
        System.out.println("staticMethod");
    }

    private StaticClass() {
    }
}

and

public class SomeClass {

    public void someMethod() {
        try {
            StaticClass.staticMethod();
        }catch(SomeException ex) {
            System.out.println("SomeException occurred");
            return;
        }
        System.out.println("SomeException didn't occur");
    }
}

which I'm testing with

@RunWith(PowerMockRunner.class)
@PrepareForTest(StaticClass.class)
public class SomeClassTest {

    @Test
    public void testStaticMethod() throws Exception {
        mockStatic(StaticClass.class);
        doThrow(new SomeException("unimportant message")).when(StaticClass.class,
                "staticMethod");
        //test something where exception is needed
        SomeClass instance = new SomeClass();
        try {
            instance.someMethod();
            fail("IllegalStateException expected");
        }catch(IllegalStateException expected) {
        }
        //now test something where exception isn't needed
        instance.someMethod();
    }
}

How can I undo the static mocking/the configuration to throw SomeException so that I can test the code after the try-catch block in the second instance.someMethod()?

PowerMock: How to unmock a method? doesn't apply because there's no mock reference to pass to Mockito.reset and passing StaticClass causes java.lang.ClassCastException: java.lang.Class cannot be cast to org.mockito.internal.creation.bytebuddy.MockAccess.

SomeException simply extends Exception.

A SSCCE is provided at https://gitlab.com/krichter/powermock-undo-statik-mocking.

I'm using PowerMock 1.7.3.

Kalle Richter
  • 8,008
  • 26
  • 77
  • 177
  • 2
    That single test is doing too much - try splitting into multiple tests such as `testStaticMethodHappyPath()` and `testStaticMethodWhenSomethingUnexpectedHappened()`. – Andrew S Feb 16 '18 at 17:39
  • I that a proposal for enhancing my test structure or should that help solving the problem. If it solves the problem, I'd be happy for an explanation, why that works. – Kalle Richter Feb 16 '18 at 21:22

2 Answers2

4

For anyone wondering how to reset PowerMocks e.x. for those pesky private static final loggers...

There was an issue (see Karl's comment in accepted solution) addressing it and the solution was to use @PrepareForTest in the method level.

so in your test method you need the annotation

/*
 * Test for MyClass.java which uses a private static final logger
 */
public class MyStaticMockTest {

  static final Logger logger = PowerMockito.mock(Logger.class);

  @BeforeClass
  public static void setup() {

    PowerMockito.mockStatic(LoggerFactory.class);
    PowerMockito.when(LoggerFactory.getLogger(MyClass.class))
        .thenReturn(MyStaticMockTest.logger);
  }


  @Test
  @PrepareForTest({LoggerFactory.class, Logger.class})
  public void testit() {
    MyClass mc = new MyClass();

    mc.methodWithSomeLogging();

    Mockito.verify(MyStaticMockTest.logger).info("some message");
  }

  @Test
  @PrepareForTest({LoggerFactory.class, Logger.class})
  public void testit() {
    MyClass mc = new MyClass();

    mc.anotherMethodWithSomeLoggingButUsingSameMessage();

    //Method will pass and not complain about info being called 2x
    Mockito.verify(MyStaticMockTest.logger, Mockito.times(1)).info("some message");
  }
}

if you want every single method reset, just put the @PrepareForTest decorator on the class instead of the method

Ryu S.
  • 1,538
  • 2
  • 22
  • 41
3

My opinion, but in general a unit test should exercise a single code path. (I think of this as applying single responsibility to a test method.)

Bu my suggestion about splitting the tests does solve the problem. I don't know the details, but @PrepareForTest provides a fresh StaticClass for each test.

These separate tests work:

@Test
public void testStaticMethodWhenSomethingUnexpectedHappens() throws Exception {
    mockStatic(StaticClass.class);
    // changed exception type
    doThrow(new IllegalStateException("unimportant message")).when(StaticClass.class, "staticMethod");

    SomeClass instance = new SomeClass();
    try {
        instance.someMethod();
        fail("IllegalStateException expected");
    } catch (IllegalStateException expected) {
    }

    // added verification
    verifyStaticMethodWasInvokedOneTime();
}

@Test
public void testStaticMethodHappyPath() throws Exception {
    mockStatic(StaticClass.class);
    doNothing().when(StaticClass.class, "staticMethod");

    SomeClass instance = new SomeClass();
    instance.someMethod();

    // added verification
    verifyStaticMethodWasInvokedOneTime();
}

private void verifyStaticMethodWasInvokedOneTime() throws SomeException {
    verifyStatic(StaticClass.class);
    StaticClass.staticMethod();
}
Andrew S
  • 2,509
  • 1
  • 12
  • 14
  • 2
    That's a good approach which allows to ease parallelization in the future. The feature to do otherwise nevertheless is discussed at https://github.com/powermock/powermock/issues/787. – Kalle Richter Feb 19 '18 at 14:28