Skip to content

Instantly share code, notes, and snippets.

@chronodm
Created February 4, 2014 21:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chronodm/8812453 to your computer and use it in GitHub Desktop.
Save chronodm/8812453 to your computer and use it in GitHub Desktop.
Unit test for Memoizer, as implemented in Java Concurrency in Practice, pp. 105-106
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.*;
import java.util.concurrent.*;
import static org.fest.assertions.Assertions.*;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;
/**
* Unit test for {@link Memoizer}, as implemented in Brian Goetz'
* "Java Concurrency in Practice", section 5.6 (listing 5.19, p. 108)
*/
public class MemoizerTest {
// ------------------------------------------------------------
// Tests
@Test
@SuppressWarnings("unchecked")
public void computeDoesCompute() throws InterruptedException {
Computable comp = mock(Computable.class);
Object key = new Object();
Object expected = new Object();
when(comp.compute(key)).thenReturn(expected);
Memoizer memoizer = new Memoizer(comp);
Object actual = memoizer.compute(key);
assertThat(actual).isSameAs(expected);
}
@Test
@SuppressWarnings("unchecked")
public void computeComputesOncePerKey() throws InterruptedException {
final int size = 5;
Map<?, ?> expected = new HashMap(){{
for (int i = 0; i < size; i++) {
put(new Object(), new Object());
}
}};
Computable comp = mock(Computable.class);
for (Map.Entry entry : expected.entrySet()) {
when(comp.compute(entry.getKey())).thenReturn(entry.getValue());
}
Memoizer memoizer = new Memoizer(comp);
for (Map.Entry entry : expected.entrySet()) {
Object actual = memoizer.compute(entry.getKey());
assertThat(actual).isSameAs(entry.getValue());
assertThat(memoizer.compute(entry.getKey())).isSameAs(actual);
}
verify(comp, times(size)).compute(any());
verifyNoMoreInteractions(comp);
}
@Test
@SuppressWarnings("unchecked")
public void computeOnlyComputesOnce() throws InterruptedException {
Computable comp = mock(Computable.class);
when(comp.compute(any())).thenReturn(new Object());
Memoizer memoizer = new Memoizer(comp);
Object key = new Object();
Object actual = memoizer.compute(key);
assertThat(memoizer.compute(key)).isSameAs(actual);
verify(comp, times(1)).compute(any());
verifyNoMoreInteractions(comp);
}
@Test
@SuppressWarnings("unchecked")
public void concurrentComputeOnlyComputesOnce() throws InterruptedException, ExecutionException, TimeoutException {
final int poolSize = 5;
final int delay = 50;
Computable comp = mock(Computable.class);
final Memoizer memoizer = new Memoizer(comp);
when(comp.compute(any())).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Thread.sleep(delay);
return new Object();
}
});
ExecutorService exec = Executors.newFixedThreadPool(poolSize);
try {
final Object key = new Object();
List<Future> futures = new ArrayList<Future>(poolSize);
for (int i = 0; i < poolSize; i++) {
futures.add(exec.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
return memoizer.compute(key);
}
}));
}
Set<Object> returned = new HashSet<Object>();
for (Future f : futures) {
returned.add(f.get(delay * poolSize, TimeUnit.MILLISECONDS));
}
assertThat(returned).hasSize(1);
verify(comp, times(1)).compute(any());
verifyNoMoreInteractions(comp);
} finally {
exec.shutdownNow();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment