0

Is it possible to mock all implementations of an interface?

I want to mock the WatchService interface like the following

public class ServiceTest {

@Test
public void callTest(
        @Capturing
        @Injectable
        final WatchService ws
) throws Exception {
    final CountDownLatch latch = new CountDownLatch(1);
    new MockUp<ServiceTest>() {
        @Mock(invocations = 1)
        public void onChange() {
            latch.countDown();
        }
    };

    new NonStrictExpectations() {
        {
            ws.take();
            result = new Delegate() {
                WatchKey take(Invocation inv) {
                    System.out.println("> " + inv.getInvokedInstance());

                    try {
                        new File("target/newFile").createNewFile();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                    return inv.proceed();
                }
            };
        }
    };

    final Thread thread = new Thread() {
        @Override
        public void run() {

            final Path target = Paths.get("target");
            final FileSystem fs = target.getFileSystem();

            try {
                try (WatchService watcher = fs.newWatchService()) {
                    target.register(watcher, ENTRY_CREATE);

                    while (!Thread.currentThread().isInterrupted()) {
                        WatchKey take = watcher.take();
                        onChange();
                        System.out.println("take " + take);
                    }

                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };

    thread.start();


    assertTrue("", latch.await(5, TimeUnit.SECONDS));
    thread.interrupt();

}

private void onChange() {
    System.out.println("CHANGE");
}

How can I accomplish that?

Cœur
  • 37,241
  • 25
  • 195
  • 267
rpvilao
  • 1,116
  • 2
  • 14
  • 31

1 Answers1

1

You can use the @Capturing annotation on a mock field or mock parameter of the interface type. Below we have a complete example test (minus imports).

public class CapturingAndProceedingTest {
    static class WatchKey { String key; WatchKey(String k) {key = k;} }
    public interface WatchService { public abstract WatchKey take(); }
    static class WatchServiceImpl1 implements WatchService {
        @Override public WatchKey take() { return new WatchKey("Abc"); }
    }
    static class WatchServiceImpl2 implements WatchService {
        @Override public WatchKey take() { return new WatchKey("123"); }
    }

    @Test
    public void mockAllImplementationsOfAnInterface(
        @Capturing  // so that all implementing classes are mocked
        @Injectable // so that Invocation#proceed() is supported
        final WatchService watchService
    ) {
        final List<WatchService> services = new ArrayList<>();

        // Record an expectation that will match all calls to 
        // WatchService#take(), on any class implementing the interface.
        new NonStrictExpectations() {{
            watchService.take();
            result = new Delegate() {
                WatchKey take(Invocation inv) throws IOException {
                    // Do something here...
                    WatchService service = inv.getInvokedInstance();
                    services.add(service);

                    // ...then proceed to the real implementation.
                    return inv.proceed();
                }
            };
        }};

        // Instantiate and use different implementations of the interface.
        WatchServiceImpl1 impl1 = new WatchServiceImpl1();
        assertEquals("Abc", impl1.take().key);
        WatchServiceImpl2 impl2 = new WatchServiceImpl2();
        assertEquals("123", impl2.take().key);

        assertEquals(Arrays.asList(impl1, impl2), services);
        System.out.println(services);
    }
}

See the JMockit Tutorial for more examples.

Rogério
  • 16,171
  • 2
  • 50
  • 63
  • Capturing will only get me a mock created by JMockit and not the real implementation of the class being mocked. I need to be able to "intercept" the call, perform some stuff and to proceed with the original implementation. If I'm not seeing it clear, please provide an example. – rpvilao Feb 26 '14 at 19:17
  • The example linked to in the answer demonstrates what you want, minus the "proceed with the original implementation" part, which can be done with `Invocation#proceed()` from a delegate method. – Rogério Feb 26 '14 at 20:29
  • Can you edit the question to include your example test using `@Capturing` which doesn't work as expected? – Rogério Feb 27 '14 at 17:45
  • Hi! Thanks for helping me with this. I was missing the @Injectable. Sorry to disappoint you on this, but it's not working because the thing is in fact intercepted and we receive the correct instance but then the code doesn't resume where it was. So the instruction in my code that goes after ws.take() (in the real implementation) doesn't run. I'll put the code in the first post. – rpvilao Feb 28 '14 at 10:54
  • Could you show a complete example test which fails? Because I can't see what the problem is from that fragment alone. – Rogério Feb 28 '14 at 15:25
  • I've edited to isolate the problem. If you try the code you should get an exception. What's there is very similar of what I want to do. Thanks one more time. – rpvilao Mar 03 '14 at 16:36
  • Ok, I ran the test and it throws a NullPointerException from the real implementation of the mocked `WatchService#take()` method, which happens to be `sun.nio.fs.AbstractWatchService#take()`. This method tries to read an internal `pendingKeys` field, which is null since the object was mocked and therefore left uninitialized. This is just as expected. I don't really understand what the test is meant to achieve, but the bottom line here is that the mocking API is not being used for mocking per se, but more like an AOP tool. So, I would probably rethink the whole test, or try to use AspectJ. – Rogério Mar 05 '14 at 17:07
  • Ok thanks for the input! It was a great help. Probably AOP here is a better fit. What you saw was me narrowing down the problem in a small test (it doesn't make sense because it was just for proof of concept). The test per se it a bit more complex and it has more dependencies. Thanks anyway for all you help I'll accept you answer anyway because I believe it answers the problem of mocking all implementations of an interface. – rpvilao Mar 06 '14 at 10:36