Skip to content

Commit 58eab9e

Browse files
committed
Unit tests for DataLoader (partially complete)
1 parent 6a42943 commit 58eab9e

File tree

1 file changed

+340
-0
lines changed

1 file changed

+340
-0
lines changed
Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
1+
/*
2+
* Copyright (c) 2016 The original author or authors
3+
*
4+
* All rights reserved. This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License v1.0
6+
* and Apache License v2.0 which accompanies this distribution.
7+
*
8+
* The Eclipse Public License is available at
9+
* http://www.eclipse.org/legal/epl-v10.html
10+
*
11+
* The Apache License v2.0 is available at
12+
* http://www.opensource.org/licenses/apache2.0.php
13+
*
14+
* You may elect to redistribute this code under either of these licenses.
15+
*/
16+
17+
package io.engagingspaces.vertx.dataloader;
18+
19+
import io.vertx.core.CompositeFuture;
20+
import io.vertx.core.Future;
21+
import io.vertx.ext.unit.junit.RunTestOnContext;
22+
import io.vertx.ext.unit.junit.VertxUnitRunner;
23+
import org.junit.Before;
24+
import org.junit.Rule;
25+
import org.junit.Test;
26+
import org.junit.runner.RunWith;
27+
28+
import java.util.*;
29+
import java.util.concurrent.Callable;
30+
import java.util.concurrent.atomic.AtomicBoolean;
31+
import java.util.stream.Collectors;
32+
33+
import static org.awaitility.Awaitility.await;
34+
import static org.hamcrest.Matchers.*;
35+
import static org.junit.Assert.assertThat;
36+
37+
/**
38+
* Tests for {@link DataLoader}.
39+
* <p>
40+
* The tests are a port of the existing tests in
41+
* the <a href="https://github.com/facebook/dataloader">facebook/dataloader</a> project.
42+
* <p>
43+
* Acknowledgments go to <a href="https://github.com/leebyron">Lee Byron</a> for providing excellent coverage.
44+
*
45+
* @author <a href="https://github.com/aschrijver/">Arnold Schrijver</a>
46+
*/
47+
@RunWith(VertxUnitRunner.class)
48+
public class DataLoaderTest {
49+
50+
@Rule
51+
public RunTestOnContext rule = new RunTestOnContext();
52+
53+
DataLoader<Integer, Integer> identityLoader;
54+
55+
@Before
56+
public void setUp() {
57+
identityLoader = idLoader(new DataLoaderOptions(), new ArrayList<>());
58+
}
59+
60+
@Test
61+
public void should_Build_a_really_really_simple_data_loader() {
62+
AtomicBoolean success = new AtomicBoolean();
63+
DataLoader<Integer, Integer> identityLoader = new DataLoader<>(keys ->
64+
CompositeFuture.all(keys.stream()
65+
.map(Future::succeededFuture)
66+
.collect(Collectors.toCollection(ArrayList::new))));
67+
68+
Future<Integer> future1 = identityLoader.load(1);
69+
future1.setHandler(rh -> {
70+
assertThat(rh.result(), equalTo(1));
71+
success.set(rh.succeeded());
72+
});
73+
identityLoader.dispatch();
74+
await().untilAtomic(success, is(true));
75+
}
76+
77+
@Test
78+
public void should_Support_loading_multiple_keys_in_one_call() {
79+
AtomicBoolean success = new AtomicBoolean();
80+
DataLoader<Integer, Integer> identityLoader = new DataLoader<>(keys ->
81+
CompositeFuture.all(keys.stream()
82+
.map(Future::succeededFuture)
83+
.collect(Collectors.toCollection(ArrayList::new))));
84+
85+
CompositeFuture futureAll = identityLoader.loadMany(Arrays.asList(1, 2));
86+
futureAll.setHandler(rh -> {
87+
assertThat(rh.result().size(), is(2));
88+
success.set(rh.succeeded());
89+
});
90+
identityLoader.dispatch();
91+
await().untilAtomic(success, is(true));
92+
assertThat(futureAll.list(), equalTo(Arrays.asList(1, 2)));
93+
}
94+
95+
@Test
96+
public void should_Resolve_to_empty_list_when_no_keys_supplied() {
97+
AtomicBoolean success = new AtomicBoolean();
98+
CompositeFuture futureEmpty = identityLoader.loadMany(Collections.emptyList());
99+
futureEmpty.setHandler(rh -> {
100+
assertThat(rh.result().size(), is(0));
101+
success.set(rh.succeeded());
102+
});
103+
identityLoader.dispatch();
104+
await().untilAtomic(success, is(true));
105+
assertThat(futureEmpty.list(), empty());
106+
}
107+
108+
@Test
109+
public void should_Batch_multiple_requests() {
110+
ArrayList<Collection> loadCalls = new ArrayList<>();
111+
DataLoader<Integer, Integer> identityLoader = idLoader(new DataLoaderOptions(), loadCalls);
112+
113+
Future<Integer> future1 = identityLoader.load(1);
114+
Future<Integer> future2 = identityLoader.load(2);
115+
identityLoader.dispatch();
116+
117+
await().until(() -> future1.isComplete() && future2.isComplete());
118+
assertThat(future1.result(), equalTo(1));
119+
assertThat(future2.result(), equalTo(2));
120+
assertThat(loadCalls, equalTo(Collections.singletonList(Arrays.asList(1, 2))));
121+
}
122+
123+
@Test
124+
public void should_Coalesce_identical_requests() {
125+
ArrayList<Collection> loadCalls = new ArrayList<>();
126+
DataLoader<Integer, Integer> identityLoader = idLoader(new DataLoaderOptions(), loadCalls);
127+
128+
Future<Integer> future1a = identityLoader.load(1);
129+
Future<Integer> future1b = identityLoader.load(1);
130+
assertThat(future1a, equalTo(future1b));
131+
identityLoader.dispatch();
132+
133+
await().until(future1a::isComplete);
134+
assertThat(future1a.result(), equalTo(1));
135+
assertThat(future1b.result(), equalTo(1));
136+
assertThat(loadCalls, equalTo(Collections.singletonList(Collections.singletonList(1))));
137+
}
138+
139+
@Test
140+
public void should_Cache_repeated_requests() {
141+
ArrayList<Collection> loadCalls = new ArrayList<>();
142+
DataLoader<String, String> identityLoader = idLoader(new DataLoaderOptions(), loadCalls);
143+
144+
Future<String> future1 = identityLoader.load("A");
145+
Future<String> future2 = identityLoader.load("B");
146+
identityLoader.dispatch();
147+
148+
await().until(() -> future1.isComplete() && future2.isComplete());
149+
assertThat(future1.result(), equalTo("A"));
150+
assertThat(future2.result(), equalTo("B"));
151+
assertThat(loadCalls, equalTo(Collections.singletonList(Arrays.asList("A", "B"))));
152+
153+
Future<String> future1a = identityLoader.load("A");
154+
Future<String> future3 = identityLoader.load("C");
155+
identityLoader.dispatch();
156+
157+
await().until(() -> future1a.isComplete() && future3.isComplete());
158+
assertThat(future1a.result(), equalTo("A"));
159+
assertThat(future3.result(), equalTo("C"));
160+
assertThat(loadCalls, equalTo(Arrays.asList(Arrays.asList("A", "B"), Collections.singletonList("C"))));
161+
162+
Future<String> future1b = identityLoader.load("A");
163+
Future<String> future2a = identityLoader.load("B");
164+
Future<String> future3a = identityLoader.load("C");
165+
identityLoader.dispatch();
166+
167+
await().until(() -> future1b.isComplete() && future2a.isComplete() && future3a.isComplete());
168+
assertThat(future1b.result(), equalTo("A"));
169+
assertThat(future2a.result(), equalTo("B"));
170+
assertThat(future3a.result(), equalTo("C"));
171+
assertThat(loadCalls, equalTo(Arrays.asList(Arrays.asList("A", "B"), Collections.singletonList("C"))));
172+
}
173+
174+
@Test
175+
public void should_Clear_single_value_in_loader() {
176+
ArrayList<Collection> loadCalls = new ArrayList<>();
177+
DataLoader<String, String> identityLoader = idLoader(new DataLoaderOptions(), loadCalls);
178+
179+
Future<String> future1 = identityLoader.load("A");
180+
Future<String> future2 = identityLoader.load("B");
181+
identityLoader.dispatch();
182+
183+
await().until(() -> future1.isComplete() && future2.isComplete());
184+
assertThat(future1.result(), equalTo("A"));
185+
assertThat(future2.result(), equalTo("B"));
186+
assertThat(loadCalls, equalTo(Collections.singletonList(Arrays.asList("A", "B"))));
187+
188+
identityLoader.clear("A");
189+
190+
Future<String> future1a = identityLoader.load("A");
191+
Future<String> future2a = identityLoader.load("B");
192+
identityLoader.dispatch();
193+
194+
await().until(() -> future1a.isComplete() && future2a.isComplete());
195+
assertThat(future1a.result(), equalTo("A"));
196+
assertThat(future2a.result(), equalTo("B"));
197+
assertThat(loadCalls, equalTo(Arrays.asList(Arrays.asList("A", "B"), Collections.singletonList("A"))));
198+
}
199+
200+
@Test
201+
public void should_Clear_all_values_in_loader() {
202+
ArrayList<Collection> loadCalls = new ArrayList<>();
203+
DataLoader<String, String> identityLoader = idLoader(new DataLoaderOptions(), loadCalls);
204+
205+
Future<String> future1 = identityLoader.load("A");
206+
Future<String> future2 = identityLoader.load("B");
207+
identityLoader.dispatch();
208+
209+
await().until(() -> future1.isComplete() && future2.isComplete());
210+
assertThat(future1.result(), equalTo("A"));
211+
assertThat(future2.result(), equalTo("B"));
212+
assertThat(loadCalls, equalTo(Collections.singletonList(Arrays.asList("A", "B"))));
213+
214+
identityLoader.clearAll();
215+
216+
Future<String> future1a = identityLoader.load("A");
217+
Future<String> future2a = identityLoader.load("B");
218+
identityLoader.dispatch();
219+
220+
await().until(() -> future1a.isComplete() && future2a.isComplete());
221+
assertThat(future1a.result(), equalTo("A"));
222+
assertThat(future2a.result(), equalTo("B"));
223+
assertThat(loadCalls, equalTo(Arrays.asList(Arrays.asList("A", "B"), Arrays.asList("A", "B"))));
224+
}
225+
226+
@Test
227+
public void should_Allow_priming_the_cache() {
228+
ArrayList<Collection> loadCalls = new ArrayList<>();
229+
DataLoader<String, String> identityLoader = idLoader(new DataLoaderOptions(), loadCalls);
230+
231+
identityLoader.prime("A", "A");
232+
233+
Future<String> future1 = identityLoader.load("A");
234+
Future<String> future2 = identityLoader.load("B");
235+
identityLoader.dispatch();
236+
237+
await().until(() -> future1.isComplete() && future2.isComplete());
238+
assertThat(future1.result(), equalTo("A"));
239+
assertThat(future2.result(), equalTo("B"));
240+
assertThat(loadCalls, equalTo(Collections.singletonList(Collections.singletonList("B"))));
241+
}
242+
243+
@Test
244+
public void should_Not_prime_keys_that_already_exist() {
245+
ArrayList<Collection> loadCalls = new ArrayList<>();
246+
DataLoader<String, String> identityLoader = idLoader(new DataLoaderOptions(), loadCalls);
247+
248+
identityLoader.prime("A", "X");
249+
250+
Future<String> future1 = identityLoader.load("A");
251+
Future<String> future2 = identityLoader.load("B");
252+
CompositeFuture composite = identityLoader.dispatch();
253+
254+
await().until((Callable<Boolean>) composite::succeeded);
255+
assertThat(future1.result(), equalTo("X"));
256+
assertThat(future2.result(), equalTo("B"));
257+
258+
identityLoader.prime("A", "Y");
259+
identityLoader.prime("B", "Y");
260+
261+
Future<String> future1a = identityLoader.load("A");
262+
Future<String> future2a = identityLoader.load("B");
263+
CompositeFuture composite2 = identityLoader.dispatch();
264+
265+
await().until((Callable<Boolean>) composite2::succeeded);
266+
assertThat(future1a.result(), equalTo("X"));
267+
assertThat(future2a.result(), equalTo("B"));
268+
assertThat(loadCalls, equalTo(Collections.singletonList(Collections.singletonList("B"))));
269+
}
270+
271+
@Test
272+
public void should_Allow_to_forcefully_prime_the_cache() {
273+
ArrayList<Collection> loadCalls = new ArrayList<>();
274+
DataLoader<String, String> identityLoader = idLoader(new DataLoaderOptions(), loadCalls);
275+
276+
identityLoader.prime("A", "X");
277+
278+
Future<String> future1 = identityLoader.load("A");
279+
Future<String> future2 = identityLoader.load("B");
280+
CompositeFuture composite = identityLoader.dispatch();
281+
282+
await().until((Callable<Boolean>) composite::succeeded);
283+
assertThat(future1.result(), equalTo("X"));
284+
assertThat(future2.result(), equalTo("B"));
285+
286+
identityLoader.clear("A").prime("A", "Y");
287+
identityLoader.clear("B").prime("B", "Y");
288+
289+
Future<String> future1a = identityLoader.load("A");
290+
Future<String> future2a = identityLoader.load("B");
291+
CompositeFuture composite2 = identityLoader.dispatch();
292+
293+
await().until((Callable<Boolean>) composite2::succeeded);
294+
assertThat(future1a.result(), equalTo("Y"));
295+
assertThat(future2a.result(), equalTo("Y"));
296+
assertThat(loadCalls, equalTo(Collections.singletonList(Collections.singletonList("B"))));
297+
}
298+
299+
@Test
300+
public void should_Resolve_to_error_to_indicate_failure() {
301+
ArrayList<Collection> loadCalls = new ArrayList<>();
302+
DataLoader<Integer, Integer> evenLoader = idLoaderWithErrors(new DataLoaderOptions(), loadCalls);
303+
304+
Future<Integer> future1 = evenLoader.load(1);
305+
evenLoader.dispatch();
306+
307+
await().until(future1::isComplete);
308+
assertThat(future1.failed(), is(true));
309+
assertThat(future1.cause(), instanceOf(IllegalStateException.class));
310+
311+
Future<Integer> future2 = evenLoader.load(2);
312+
evenLoader.dispatch();
313+
314+
await().until(future2::isComplete);
315+
assertThat(future2.result(), equalTo(2));
316+
assertThat(loadCalls, equalTo(Arrays.asList(Collections.singletonList(1), Collections.singletonList(2))));
317+
}
318+
319+
@SuppressWarnings("unchecked")
320+
private static <K, V> DataLoader<K, V> idLoader(DataLoaderOptions options, List<Collection> loadCalls) {
321+
return new DataLoader<>(keys -> {
322+
loadCalls.add(new ArrayList(keys));
323+
List<Future> futures = keys.stream().map(Future::succeededFuture).collect(Collectors.toList());
324+
return CompositeFuture.all(futures);
325+
}, options);
326+
}
327+
328+
@SuppressWarnings("unchecked")
329+
private static DataLoader<Integer, Integer> idLoaderWithErrors(
330+
DataLoaderOptions options, List<Collection> loadCalls) {
331+
return new DataLoader<>(keys -> {
332+
loadCalls.add(new ArrayList(keys));
333+
List<Future> futures = keys.stream()
334+
.map(key -> key % 2 == 0 ? Future.succeededFuture(key) :
335+
Future.failedFuture(new IllegalStateException("Error")))
336+
.collect(Collectors.toList());
337+
return CompositeFuture.all(futures);
338+
}, options);
339+
}
340+
}

0 commit comments

Comments
 (0)