JMockit - 期望返回旧值而不是与私有字段关联的值

时间:2015-10-15 12:51:32

标签: java unit-testing mocking jmockit

当记录返回字段值的期望时,我希望返回的值是调用实际方法时的字段值(引用的值),而不是期望字段的值记录了。

这是被测试的课程(实际上是2个):

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();

}

这是我的测试类:

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;
    }
}

}

这个测试通过了,但是我希望有一个更优雅的方法来设置acquireList()方法的返回值的期望值,因为这种“willReturn”方法一旦我有超过这个方法就会变成维护噩梦其中一个在同一个班级。

我正在寻找类似于mockito-syntax命令的东西:

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

这应始终返回provList字段的当前值(而不是记录期望时的值)。

修改 在完成文档后,我想出了一个解决方案,使用代表:

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

这种方法存在两个问题:
1.它不优雅 2. List<Integer> delegate()方法导致编译时警告:

  

来自类型new Delegate&gt;(){}的方法delegate()永远不会   本地使用

因此,仍在寻找另一种解决方案

2 个答案:

答案 0 :(得分:1)

OP试图解决的问题&#34;是这样的:当测试中的代码(这里是obsCache.getItems()方法)和要执行的验证相同但输入值不同时,如何在单个测试方法中简化多个测试的编写。 / p>

所以,这真的是一个关于如何正确编写测试的问题。编写良好的测试的基本形式由"Arrange-Act-Assert" (AAA)模式描述:

@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

    // ...
}

显然,像上面第二个这样的测试被认为是不好的做法,不应该被鼓励。

编辑:为真正的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);
    }
}

答案 1 :(得分:0)

在代码中使用新的Expectations()时,会使用providedInt值创建Expectations实例。因此,尽管在testRegisterInt()中更改了provideInt,但Expectations实例的状态不会更改。您可以尝试使用setter来更改Expectations的结果。

理想情况下,存根中不应有任何逻辑。我宁愿在多个测试方法中创建多个存根(如果它真的需要),或者根据我的需要使用anyInteger类型的东西。