1

I have an enumeration type which I used as a collection of factories for other objects that implement a common interface. A simplified version of the code is:

interface Operation { 
    void execute();
}

enum Factory {
    TYPE1(Class1::new),
    TYPE2(Class2::new);

    private Supplier<Operation> constructor;

    Factory(Supplier<Operation> constructor) {
        this.constructor = constructor;
    }

    Operation build() {
        return constructor.get(); 
    }
}

A simplified version of the client code looks like:

class Client {
    private void run(EnumSet<Factory> required) {
        for (Factory x : required) {
            x.build().execute(); 
        }
    }

    // Some code that calls run() with the right EnumSet
}

This all appears to work as expected, so I want to write some Unit tests.

Testing the Factory is easy, but the Client is proving more difficult. The problem is that I don't want to start calling the Operation (they do a lot of work). Instead I'd like to get x.build() return a mock object.

I've tried using PowerMocks whenNew to trap the creation of the Operation objects, but this doesn't work (I don't actually have any 'new' operations). I've also tried to use a Powermock 'spy', but this fails because the enumeration constants are real objects.

Any ideas?

Stormcloud
  • 2,065
  • 2
  • 21
  • 41
  • Any feedback on my answer? If you found it helpful, pls dont forget about accepting, otherwise let me know if I can add anything to it ... – GhostCat Nov 15 '18 at 15:50
  • Hi, I've been playing with the Powermock solution over the last couple of days, but I've not been able to get it to work :-( In the linked question the enumeration contained an abstract method which is why (I think) the individual constants have their own class. In this case there are no abstract methods, so the compiler only produces a single class file. – Stormcloud Nov 16 '18 at 11:15
  • I see. But as said: I consider PowerMock(ito) to always be the last straw. If possible, change your production code, and make it easier to test. Anything else is using black magic to circumvent a designed-in deficiency. – GhostCat Nov 16 '18 at 11:30
  • For the moment my work around is to add a setter function to the enumeration constants so that `build()` can return prebuilt objects - my mocks. While it means I've only made a small change to the production code it does mean that my enumeration 'constant' is now mutable! This feels worse then the alternatives. – Stormcloud Nov 16 '18 at 11:58

1 Answers1

1

Two solutions:

  • you insist on using PowerMock(ito). Then the simple issue is: you are probably preparing the wrong class for test. You have to temper with those enum constants, and they are inner classes. But it is possible, see this answer for example.
  • you step back, and improve your production code, to make it easier to test.

For example like this:

interface OperationProvider { 
    Operation build();
}

to be used like:

enum Factory implements OperationProvider {

And now you can change your client code to do

OperationProvider ourProvider = ...
whatever.build();...

The point is: when using dependency injection, you simply pass a mocked OperationProvider. This means that you can basically sidestep the need to mock with your enum completely. You just have to make sure that within your production code, you obviously pass one of the enum constants when initializing that ourProvider field for example.

GhostCat
  • 137,827
  • 25
  • 176
  • 248