Search code examples
javaunit-testingmockingjmockit

JMockit - expectation returns old value instead of value associated with private field


When recording an expectation that returns the value of a field, I would expect the returned value to be the value of the field when the actual method was invoked (value of the reference), as opposed to the field's value when the expectation was recorded.

This is the class under test (actually 2 of them):

public class ListObservingCache<T> extends ObservingCache {
public ListObservingCache(Supplier<List<T>> syncFunc, int intervalMillis) {
    super(syncFunc, intervalMillis);
}

@SuppressWarnings("unchecked")
@Override
public List<T> getItems() {     
    return items != null ? Collections.unmodifiableList((List<T>) items) : null;
}
}



public abstract class ObservingCache {
private static final int DEFAULT_CACHE_REFRESH_INTERVAL = 10 * 60 * 1000; // 10 minutes
private static int DEFAULT_CACHE_INITIAL_DELAY = 10 * 60 * 1000; // 10 minutes
private static final int DEFAULT_THREAD_POOL_SIZE = 5;

private static ScheduledExecutorService executor;

protected Object items;

protected ObservingCache(Supplier<? extends Object> syncFunc) {
    this(syncFunc, DEFAULT_CACHE_REFRESH_INTERVAL);
}

protected ObservingCache(Supplier<? extends Object> syncFunc, int intervalMillis) {
    if (executor == null || executor.isShutdown()) {
        executor = Executors.newScheduledThreadPool(DEFAULT_THREAD_POOL_SIZE);
    }
    Runnable task = () -> {
        Object result = syncFunc.get();
        if (result != null) {
            items = result;
        }
    };
    task.run(); // First run is blocking (saves a lot of trouble later).
    executor.scheduleAtFixedRate(task, DEFAULT_CACHE_INITIAL_DELAY, intervalMillis, TimeUnit.MILLISECONDS);
}

public abstract Object getItems();

}

Here is my test class:

public class ListObservingCacheTest {
List<Integer> provList; // <-- The field I wish to use instead of the "willReturnList()" method

@Mocked
DummyTask mockTask;

@BeforeClass
public static void setupClass() {
    ObservingCache.DEFAULT_CACHE_INITIAL_DELAY = 100;
}

@AfterClass
public static void tearDownClass() {
    ExecutorService toShutDown = (ExecutorService) getField(ObservingCache.class, "executor");
    toShutDown.shutdown();
}

@Before
public void setUp() {
    mockTask = new DummyTask(); // Empty list
}

@Test
public void testBasic() throws Exception {
    willReturnList(Arrays.asList(1, 2));
    ListObservingCache<Integer> obsCache = new ListObservingCache<Integer>(() -> mockTask.acquireList(), 300);
    assertEquals(Arrays.asList(1, 2), obsCache.getItems());
    willReturnList(Arrays.asList(3, 4, 5));
    assertEquals(Arrays.asList(1, 2), obsCache.getItems()); // ObservingCache should still returns the former list because its interval hasn't passed yet
    Thread.sleep(300);
    assertEquals(Arrays.asList(3, 4, 5), obsCache.getItems()); // ObservingCache should now return the "new" list, as its interval has passed and the task has been executed again
}

/**
 * Instructs the mock task to return the specified list when its
 * acquireList() method is called
 */
private void willReturnList(List<Integer> list) {
    new Expectations() {{ mockTask.acquireList(); result = list; }};
}

/**
 * Simulates an ObservingCache "real-life" task. Should never really be
 * called (because it's mocked).
 */
class DummyTask {
    private List<Integer> list;

    public List<Integer> acquireList() {
        return list;
    }
}

}

This test passes, but I would like a more elegant way to set the expectation for the return value of the acquireList() method, as this kind of "willReturn" methods would become a maintenance nightmare once I have more than one of these in the same class.

I'm looking for something similar to the mockito-syntax command:

when(mockTask.acquireList()).thenReturn(provList);

This should always return the current value of the provList field (as opposed to its value when the expectation was recorded).

EDIT: After going through the documentation, I came up with a solution, using delegates:

new Expectations() {{
                mockTask.acquireList();
                result = new Delegate<List<Integer>>() {
                    List<Integer> delegate() {
                        return provList; // The private field
                    }
                };
            }};

There are 2 problems with this approach:
1. It's not elegant
2. The List<Integer> delegate() method causes a compile-time warning:

The method delegate() from the type new Delegate>(){} is never used locally

Therefore, still looking for another solution


Solution

  • The problem the OP is trying to "solve" is this: how to simplify the writing of multiple tests in a single test method, when the code under test (here, the obsCache.getItems() method) and the verifications to perform are the same, but the input values are different.

    So, this is really a question about how to properly write tests. The basic form of a well-written test is described by the "Arrange-Act-Assert" (AAA) pattern:

    @Test
    public void exampleOfAAATest() {
        // Arrange: set local variables/fields with input values, 
        // create objects and/or mocks, record expectations.
    
        // Act: call the code to be tested; normally, this is *one* method
        // call only.
    
        // Assert: perform a number of assertions on the output, and/or
        // verify expectations on mocks.
    }
    
    @Test
    public void exampleOfWhatisNotAnAAATest() {
        // First "test":
        // Arrange 1
        // Act
        // Assert 1
    
        // Second "test":
        // Arrange 2 (with different inputs)
        // Act again
        // Assert 2
    
        // ...
    }
    

    Obviously, tests like the second one above are regarded as bad practice, and should not be encouraged.

    EDIT: added full test class (below) for the real CUT.

    public final class ListObservingCacheTest
    {
        @Mocked DummyTask mockTask;
        final int refreshIntervalMillis = 30;
        final List<Integer> initialItems = asList(1, 2);
        final List<Integer> newItemsAfterRefreshInterval = asList(3, 4, 5);
    
        @Before
        public void arrangeTaskOutputForMultipleCalls() {
            new Expectations() {{
                mockTask.acquireList();
                result = initialItems;
                result = newItemsAfterRefreshInterval;
            }};
    
            // A trick to avoid a long initial delay before the scheduled task is first
            // executed (a better solution might be to change the SUT to read the
            // initial delay from a system property).
            new MockUp<ScheduledThreadPoolExecutor>() {
                @Mock
                ScheduledFuture<?> scheduleAtFixedRate(
                    Invocation inv,
                    Runnable command, long initialDelay, long period, TimeUnit unit
                ) {
                    return inv.proceed(command, 0, period, unit);
                }
            };
        }
    
        @After
        public void shutdownTheExecutorService() {
            ScheduledExecutorService executorService = 
                Deencapsulation.getField(ObservingCache.class, ScheduledExecutorService.class);
            executorService.shutdown();
        }
    
        @Test
        public void getTheInitialItemsImmediatellyAfterCreatingTheCache() throws Exception {
            // Arrange: empty, as there is nothing left to do beyond what the setup method
            // already does.
    
            // Act:
            ListObservingCache<Integer> obsCache = 
                new ListObservingCache<>(() -> mockTask.acquireList(), refreshIntervalMillis);
            List<Integer> items = obsCache.getItems();
    
            // Assert:
            assertEquals(initialItems, items);
        }
    
        @Test
        public void getTheSameItemsMultipleTimesBeforeTheCacheRefreshIntervalExpires() throws Exception {
            // Act:
            ListObservingCache<Integer> obsCache = 
                new ListObservingCache<>(() -> mockTask.acquireList(), refreshIntervalMillis);
            List<Integer> items1 = obsCache.getItems();
            List<Integer> items2 = obsCache.getItems();
            List<Integer> itemsIfTaskGotToBeCalledAgain = mockTask.acquireList();
            List<Integer> items3 = obsCache.getItems();
    
            // Assert:
            assertEquals(initialItems, items1);
            assertEquals(initialItems, items2);
            assertEquals(initialItems, items3);
            assertEquals(newItemsAfterRefreshInterval, itemsIfTaskGotToBeCalledAgain);
        }
    
        @Test
        public void getNewItemsAfterTheCacheRefreshIntervalExpires() throws Exception {
            // Act:
            ListObservingCache<Integer> obsCache = 
                new ListObservingCache<>(() -> mockTask.acquireList(), refreshIntervalMillis);
            List<Integer> items1 = obsCache.getItems();
            Thread.sleep(refreshIntervalMillis);
            List<Integer> items2 = obsCache.getItems();
    
            // Assert:
            assertEquals(initialItems, items1);
            assertEquals(newItemsAfterRefreshInterval, items2);
        }
    }