0

I have an enum class with an overloaded from method like so:

//actual implementation has @Service annotation
public interface Service {
    enum Level {
        NO(0),
        YES(1),
        SOME(2)
        
        public final Integer value;

        Level(Integer value) {
            this.value = value;
        }

        public static Level from(Boolean value) {
            return Boolean.TRUE.equals(value) ? YES : NO;
        }

        public static Level from(Level value) {
            return value;
        }
    }

//I didn't create this, but the reason for the overloaded from is that we're currently using the Boolean version, but will possibly switch that up later so the overload offers some future proofing I presume.

from is currently only used with this method: public Boolean getLevel(), therefore it can only return YES or NO in reality, however behavior does exist for SOME that I want to test. However I cannot get that value without mocking from

This behavior is in another class that calls Service.Level.from with service.getLevel():

@Component
public class Impl

    private final Service service;
    private final OtherClass otherClass;

    public void work() {
        Service.Level level = Service.Level.from(service.getLevel());
        switch (level) {
            case YES:
                otherClass.someMethod();
                break;
            case NO:
                otherClass.someOtherMethod();
                break;
            case SOME:
                otherClass.thirdMethod();
                break;
        }
        ...
    }

Here's my test:

@RunWith(MockitoJUnitRunner.class)
public class ImplTest {

    @Mock
    private Service service;

    @Mock
    private OtherClass otherClass;

    @InjectMocks
    private Impl impl;

    @Test
    public void testYes {
        doReturn(Boolean.TRUE).when(service).getLevel();

        impl.work();

        verify(otherClass).someMethod();
    }

    @Test
    public void testNo {
        doReturn(Boolean.FALSE).when(service).getLevel();

        impl.work();

        verify(otherClass).someOtherMethod();
    }

    @Test
    public void testSome {
        doReturn(Boolean.FALSE).when(service).getLevel();

        try (MockedStatic<Service.Level> mockedStatic = mockStatic(Service.Level.class)) {
            mockedStatic.when(() -> Service.Level.from(any(Boolean.class))
                    .thenReturn(Service.Level.Full);
        }
        impl.work();

        verify(otherClass).thirdMethod();
    }

}

The first two tests pass as expected but the third one doesn't end up mocking from and returns NO, which is its unmocked behavior. I also attempted to make the staticMock class-level:

private static MockedStatic<Service.Level> mockedStatic;

@BeforeClass
public static void beforeClass {
    mockedStatic = mockStatic(Service.Level.class);
}

...

then call that with 
mockedStatic.when(() -> Service.Level.from(any(Boolean.class)).thenReturn(/*test-appropriate return value here*/);
//example:
 @Test
    public void testNo {
        //
        doReturn(Boolean.FALSE).when(service).getLevel();

        mockedStatic.when(() -> Service.Level.from(any(Boolean.class)).thenReturn(Service.Level.NO);

        impl.work();

        verify(otherClass).someOtherMethod();
    }


but then I ran into ExceptionInInitializerError and then NoClassDefFoundError, the former caused by a NullPointerException in switch() in Impl.work(), however calling Service.Level.from with a debugger does return the appropriate enum constant, only for the actual method call to then fail. I suspect this might have to do with Service being mocked and its inner class Service.Level being static mocked, however the thrown issues do not even hint towards that.
How can I solve this issue?
Apologies that I'm not posting the actual error messages but that would reveal my company and some class names that I'd rather avoid.

  • Does this actually compile? Doesn't the `from` method have to be **`static`**? – knittl Oct 17 '22 at 10:08
  • @knittl Yes, it is, apologies, the post has been updated – András Ballai Oct 17 '22 at 11:29
  • When you mock `service.getLevel()`, then `from` should never be called. It could be during setup, if you are not mocking interfaces, but classes; but it will never be called in your real test. Also check [Why is my class not calling my mocked methods in unit test?](https://stackoverflow.com/questions/74027324/why-is-my-class-not-calling-my-mocked-methods-in-unit-test). Perhaps some of it applies here too. – knittl Oct 17 '22 at 11:34
  • You can still post the stack trace by simply blanking out any PII. Simply replace your package names and sensitive class names with "xxxx" or "[redacted]" – knittl Oct 17 '22 at 11:34
  • @knittl I'm fairly sure `from` is in fact getting called, as the mocked value `service.getLevel()` does properly get transformed to `YES` and `NO` in their respective test cases when `Level` isn't mocked (as from is a method of `Level`, not of `Service`). But could that still be the issue? That `Service` itself is mocked - which may include `Level` too, but then I'm also creating a mock of `Level`, or does `Level` remain intact during the mocking of `Service`? – András Ballai Oct 17 '22 at 12:15
  • `doReturn(Boolean.TRUE).when(service).getLevel()` setsup up the mock. `Level.from` will not be called, _unless_ your SUT is not using your mock. For the latter, refer to the linked question from my previous comment. – knittl Oct 17 '22 at 12:20

0 Answers0