The subject
I have some code that is decidedly not thread safe:
public class ExampleLoader
{
private List<String> strings;
protected List<String> loadStrings()
{
return Arrays.asList("Hello", "World", "Sup");
}
public List<String> getStrings()
{
if (strings == null)
{
strings = loadStrings();
}
return strings;
}
}
Multiple threads accessing getStrings()
simultaneously are expected to see strings
as null
, and thus loadStrings()
(which is an expensive operation) is triggered multiple times.
The problem
I wanted to make the code thread safe, and as a good citizen of the world I wrote a failing Spock spec first:
def "getStrings is thread safe"() {
given:
def loader = Spy(ExampleLoader)
def threads = (0..<10).collect { new Thread({ loader.getStrings() })}
when:
threads.each { it.start() }
threads.each { it.join() }
then:
1 * loader.loadStrings()
}
The above code creates and starts 10 threads that each calls getStrings()
. It then asserts that loadStrings()
was called only once when all threads are done.
I expected this to fail. However, it consistently passes. What?
After a debugging session involving System.out.println
and other boring things, I found that the threads are indeed asynchronous: their run()
methods printed in a seemingly random order. However, the first thread to access getStrings()
would always be the only thread to call loadStrings()
.
The weird part
Frustrated after quite some time spent debugging, I wrote the same test again with JUnit 4 and Mockito:
@Test
public void getStringsIsThreadSafe() throws Exception
{
// given
ExampleLoader loader = Mockito.spy(ExampleLoader.class);
List<Thread> threads = IntStream.range(0, 10)
.mapToObj(index -> new Thread(loader::getStrings))
.collect(Collectors.toList());
// when
threads.forEach(Thread::start);
threads.forEach(thread -> {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// then
Mockito.verify(loader, Mockito.times(1))
.loadStrings();
}
This test consistently fails due to multiple calls to loadStrings()
, as was expected.
The question
Why does the Spock test consistently pass, and how would I go about testing this with Spock?