vendredi 3 juillet 2015

Avoid nullary constructors used only for testing purposes - Allow mocked object's method to throw exception

I want to test a method of a class. This class has 1 constructor with 3 parameters, which I can only supply one of the 3 requested.

I cannot mock the object because I am testing for invalid use, so I need the test to expect an exception. Mocking the object results in no exception being thrown.

The problem with mocking

public class NodeHandler {
    private List<Node> nodes;
    private Node currentNode;

    public NodeHandler(List<Node> nodes, Object obj1, Object obj2) {
        this.nodes = nodes;
        //uses obj1 and obj2; they cannot be null
    }

    public void initCurrentNode() {
        for(Node node : nodes) {
            if(node.canProcess()) {
                currentNode = node;
                return;
            }
        }

        throw new IllegalStateException("No nodes can be processed");
    }
}

The method I'm testing only depends on List<Node> nodes.

I manually initialize this myself (in the test) by accessing the field through reflection and setting the value:

public class MyTest {
    private NodeHandler mocked = Mockito.mock(NodeHandler.class);

    @Test(expected = IllegalStateException.class)
    public void testInvalidUsage() throws Exception {
        List<Node> nodes = new ArrayList<>();
        nodes.add(FirstNode.class.newInstance());
        nodes.add(SecondNode.class.newInstance());

        Field field = NodeHandler.class.getDeclaredField("nodes");
        field.setAccessible(true);
        field.set(mocked, nodes);

        try {
            method.invoke(manager);
        } catch (IllegalAccessException | IllegalArgumentException e) {
            throw new InvocationTargetException(e);
        } catch (InvocationTargetException e) {
            throw (IllegalStateException) e.getTargetException();
        }
    }

    public static class FirstNode extends Node {
        public boolean canProcess() { return false; }
        public void process() { }
    }


    public static class SecondNode extends Node {
        public boolean canProcess() { return false; }
        public void process() { }
    }
}

This test fails, since it expects an IllegalStateException, but one is not thrown (due to the mock object).

The nasty work-around

I could declare a private nullary constructor in NodeHandler, set it to accessible. This will allow me to create an object whose methods may throw exceptions:

class NodeHandler {
    //...

    private NodeHandler() { }

    //...
}

public class MyTest {
    private NodeHandler manager;

    @Before
    public void init() throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        Constructor<?> constructor = NodeHandler.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        manager = (NodeHandler) constructor.newInstance();
    }

    @Test(expected = IllegalStateException.class)
    public void testInvalidUsage() throws Exception {
        //same as before
    }
}

This give me the results I want, but seems "hackish". I don't want to be required to declare a private constructor in every class I want to test.

My Question

Without being forced to declare a private nullary constructor, how could I test a method that's expected to throw an exception from an object without being required to fill all the parameters of the constructor for that object? Is there a way I can mock an object while still accounting for exceptions thrown by methods?

Aucun commentaire:

Enregistrer un commentaire