2
\$\begingroup\$

I'm playing around with JMock and trying to understand whether I understand the idea correctly.

There's an interface Printer aimed to print integers somewhere. There's an interface Calculator aimed to perform math. There's a class CalculatingMachine aimed to connect Calculator and Printer: first, it calculates the result with Calculator and then this result is passed to Printer. Here's the code

public class CalculatingMachine {       
    private final Printer printer;
    private final Calculator calculator;

    public CalculatingMachine(Printer printer, Calculator calculator) {
        this.printer = printer;
        this.calculator = calculator;
    }

    public void processAdd(int x, int y) {
        int result = calculator.add(x, y);
        printer.print(result);
    }

    public static interface Printer {
        void print(int x);
    }

    public static interface Calculator {
        int add(int x, int y);
    }   
}

As far as I understand CalculatingMachine only requires one test: we need to make sure IT USES calculator to do the math and then we need to make sure IT USES printer to print the result. So, here's my test:

public class CalculatingMachineTest {       
    @Test
    public void testCalculatingMachine() {
        Mockery context = new JUnit4Mockery();

        final Printer printer = context.mock(Printer.class);
        final Calculator calculator = context.mock(Calculator.class);

        context.checking(new Expectations() {{
            oneOf(calculator).add(1, 2);
            will(returnValue(3));

            oneOf(printer).print(3);            
        }});

        CalculatingMachine machine = new CalculatingMachine(printer, calculator);
        machine.processAdd(1, 2);

        context.assertIsSatisfied();
    }
}

Pretty straightforward and does what it supposed to do. There are 2 points I'm curious about:

  1. Is it the right approach even though CodePro says that CalculatingMachine itself has 19 lines of code, though test for it is 23 lines of code? I'm just not sure: is it normal to write more tests then code that does something?
  2. Is there any other way to test all the same but with less code?
\$\endgroup\$
3
\$\begingroup\$

This is technically correct code, whether it's right depends...

This might be the first test for an object that will become more complicated. It's reasonable to start small and work your way up.

If the Calculator is a self-contained implementation that doesn't pull in other dependencies, it might be better to just use a real one. In this case, it looks more like a function.

In the end, there might well be more test than production code because you want to exercise all the paths. These days, I find myself writing unit tests with mocks at a slightly higher level, around a small cluster of objects.

If the call to the Calculator doesn't change the state of the world outside the Machine, I'd probably use an allowing clause, we "Stub Queries, Expect Actions"

allowing(calculator).add(1,2); will(returnValue(3));

it's the same implementation underneath but expresses the intent more clearly.

You might try the JUnitRuleMockery with JUnit's @Rule. It takes care of the assertion housekeeping at the right time.

And before someone jumps in with their favourite alternative mocking framework, I'm not sure that's really the point. We can shave a few lines off here and there, but it's more important to understand the design issues. And everything will change with Java 8.

\$\endgroup\$
2
\$\begingroup\$

It's completely normal to have more test code than production code. See:

You can make it a little bit shorted if you use the @RunWith(JMock.class) annotation and move the fixture to class fields:

import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JMock;
import org.jmock.integration.junit4.JUnit4Mockery;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(JMock.class)
public class CalculatingMachineJMockTest {

    private final Mockery context = new JUnit4Mockery();

    private final Printer printer = context.mock(Printer.class);
    private final Calculator calculator = context.mock(Calculator.class);

    @Test
    public void testCalculatingMachine() {
        context.checking(new Expectations() {
            {
                oneOf(calculator).add(1, 2);
                will(returnValue(3));

                oneOf(printer).print(3);
            }
        });

        final CalculatingMachine machine = new CalculatingMachine(printer, calculator);
        machine.processAdd(1, 2);
    }

}

It helps more if you have more than one test method.


Another note: does it make sense to create a CalculatingMachine with null printer or calculator? I'd check these nulls in the constructor. checkNotNull from Guava is a great choice for this.

    public CalculatingMachine(final Printer printer, final Calculator calculator) {
        this.printer = checkNotNull(printer, "printer cannot be null");
        this.calculator = checkNotNull(calculator, "calculator cannot be null");
    }

And here are two test:

    @Test(expected = NullPointerException.class)
    public void testNullPrinter() throws Exception {
        new CalculatingMachine(null, calculator);
    }

    @Test(expected = NullPointerException.class)
    public void testNullCalculator() throws Exception {
        new CalculatingMachine(printer, null);
    }

(See: Effective Java, 2nd edition, Item 38: Check parameters for validity)

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.