Is it possible to mock an enum with Spock?
Asked Answered
D

3

6

I have a switch statement to handle an java enum foo, and am using spock to write some groovy unit tests. I have already added a test which verifies that every type of foo is currently handled without throwing an exception. Now I want to test that an unrecognised type of foo will cause an exception to be thrown.

To do this I will have to mock an enum, and have already seen the solution outlined here: Mocking Java enum to add a value to test fail case

I also know that it is possible to do with powermock, but I really like spock, as I find it incredibly lightweight and so was looking for a solution using spock.

I thought something like this might have worked:

    def "An unexpected type of foo causes an exception to be thrown"() {
        given:
        Foo foo = Mock()
        when:
        subjectUnderTest.handleFoo foo
        then:
        thrown Exception
}

However, this fails with the following error message: org.spockframework.mock.CannotCreateMockException: Cannot create mock for class com.Foo because Java mocks cannot mock final classes. If the code under test is written in Groovy, use a Groovy mock.

I was wondering if anybody knew of a way to do this using spock, as I can't find a solution on any documentation.

Edit After some of the comments below, I feel it is best to clarify why I want to write these tests.

I work in a team with quite a few developers, and it is quite possible that one of them will update the enum. I want tests to fail if this happens to make the developer aware that they need to add logic to handle the new enum type. For this, I wrote a test that iterates over every possible value that the enum could have, passed it into the method and verify that no exception is thrown. Now, if a user added a new enum, this test would fail as there is nothing to handle it so an exception would (hopefully) be thrown.

The second test (the one I am struggling to write) is to clarify that the default logic works as I would expect it to, and that an exception does in fact get thrown. The only way I can think of doing this without creating an enum value that I will never want to be used is to mock an enum, and test that an exception gets thrown if the enum value is not handled.

The code looks like this:

Enum Foo:

public enum Foo {
    ONE, TWO;
}

Method to handle foo:

private void handleFoo(Foo foo) {
    switch (foo) {
        case ONE:
           doEventOne();
           break;
        case TWO:
           doEventTwo()
           break;
        default:
           throw new IllegalArgumentException("Do not know how to handle " + foo);
}
Dorotea answered 13/3, 2015 at 14:14 Comment(10)
Provide sample test please.Pamper
@Pamper I have added the sample test above.Dorotea
Do we need to mock enum?Villainous
@Ramsharan, not sure I understand. What alternative is there?Dorotea
@BenGreen Can you post the actual code logic for which you are trying to write test with mocking enum?Villainous
@BenGreen, just clarify the testing scenario, it seems that what you want ti test will never happen.Pamper
@Villainous I have added the code, and a bit more clarification around what it is I am trying to do.Dorotea
@Pamper I have added some clarification above. As you can see, it isn't currently possible to hit the default logic, but no reason why it couldn't be in the future.Dorotea
@BenGreen If the enum was groovy enum, you can do "new Foo()" or you can use ' Foo f = GroovyMock(Foo); f.equals(_) >> false' and pass it to the function to test. But you are asking for java enum so no way.Villainous
Ok fair enough. Will have to use powermock or something. Thanks for the help guys.Dorotea
P
6

The following specification will cover adding new enum without providing service logic for it:

def 'no exception thrown for all enums'() {
    given:
    def service = new SampleService()

    when:
    service.handleFoo(se)

    then:
    noExceptionThrown()

    where:
    se << SampleEnum.values()
}

I've tried to write a test that will mock the enum or add another value in test runtime, however failed for now. Will back to it later on.

Spock doesn't support mocking enums.

Pamper answered 14/3, 2015 at 11:34 Comment(4)
Thanks. I've actually already got this test. As I say, only struggling with the testing of the default block.Dorotea
@BenGreen, sure. As I said will return to it later on. However spock doesn't support mocking enums.Pamper
@BenGreen, I've tried to use EnumBuster: javaspecialists.eu/archive/Issue161.html, it works perfectly with java however have some problems with groovy. I won't fix it in a reasonable time so you can consider my answer as being final for now.Pamper
Ok thanks. I'll accept your answer based on the "Spock doesn't support mocking enums" bit. Thanks for all the work.Dorotea
Z
0

This question is old and I was thinking about how to cover the default: branch. I also looked at the EnumBuster workaround mentioned in a comment. Here is an EnumBuster update for more recent Java versions, by the way.

Anyway, I think that using tools - I would rather call it dirty tricks - like that is not a particularly good idea. Probably, adding a static code analysis tool covering that case to your build configuration would be better. I quickly checked the default rule sets for Checkstyle, PMD, SonarQube and FindBugs, but did not find rules exactly doing what we need here, but both Eclipse and IntelliJ IDEA have built-in inspections for it. They are also configurable, i.e. to issue warnings or build errors, to still warn/fail if there is a default: clause or not:

Eclipse inspection

IDEA inspection

Maybe somewhere out there is a custom rule for one of the popular static code analysis tools which I just did not find. Of course you could also write a custom rule. The advantage would be that it would work for the whole project or potentially many projects, be re-usable and would not require an extra test.

If you are obsessed with test coverage for this corner case, the static code analysis tool would not help you, of course. Then you could still put a custom version of the enum with an extra dummy value on your test class path (possibly auto-generated as a derivation of the original source file) in order to cover the default case or an exception thrown and otherwise never reached after a switch block.

Zymogenic answered 15/2, 2021 at 4:10 Comment(0)
C
0

As explained here https://www.baeldung.com/java-extending-enums you can create an interface for the Enum and mock the interface.

Coverdale answered 11/12, 2021 at 11:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.