This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class FileCreator { | |
private ExecutorService executor = Executors.newCachedThreadPool(); | |
public void createFile(final String fileName) { | |
executor.submit(new Callable<Path>() { | |
@Override | |
public Path call() throws Exception { | |
// simulate delay | |
Thread.sleep(500); | |
return Files.createFile(Paths.get(fileName)); | |
} | |
}); | |
} | |
} |
We have a class that creates file based on a given file name. The interesting thing is, that it does it asynchronously using a thread pool and returns immediately to the caller.
Let's try to create a test for it. Our first attempt could look like that:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class FileCreatorTestWithSleeping { | |
private FileCreator fileCreator = new FileCreator(); | |
private String fileName = "file.txt"; | |
@Before | |
public void setUp() throws IOException { | |
Files.deleteIfExists(Paths.get(fileName)); | |
} | |
@Test | |
public void shouldCreateFile() throws InterruptedException { | |
// when | |
fileCreator.createFile(fileName); | |
Thread.sleep(1000); | |
// then | |
verifyThatFileExists(fileName); | |
} | |
private void verifyThatFileExists(String fileName) { | |
Path path = Paths.get(fileName); | |
assertThat(Files.exists(path), is(true)); | |
} | |
} |
The horrible thing about it is that Thread.sleep() invocation. Test should be fast, making them wait unnecessary is very poor solution. And what if the test sometimes fails because of overloaded hardware? Are we going to sleep even more?
To eliminate unneeded waiting, we may come up with a concept of validator:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class FileCreatorTestWithCustomValidator { | |
private FileCreator fileCreator = new FileCreator(); | |
private String fileName = "file.txt"; | |
@Before | |
public void setUp() throws IOException { | |
Files.deleteIfExists(Paths.get(fileName)); | |
} | |
@Test | |
public void shouldCreateFile() throws InterruptedException { | |
// when | |
fileCreator.createFile(fileName); | |
// then | |
boolean fileExists = checkThatFileExists(fileName); | |
assertThat(fileExists, is(true)); | |
} | |
private boolean checkThatFileExists(String fileName) | |
throws InterruptedException { | |
for (int i = 0; i < 100; i++) { | |
Path path = Paths.get(fileName); | |
try { | |
assertThat(path, exists()); | |
return true; | |
} catch (AssertionError e) { | |
// ignore exception | |
} | |
Thread.sleep(100); | |
} | |
throw new AssertionError("Timeout exceeded"); | |
} | |
} |
We no longer need to sleep for a long time, but the code has been significantly polluted. Of course we can refactor our validator, make it more reusable but why reinvent the wheel? There is a nice and small library - awaitility - that will do the same for us.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class FileCreatorTestWithAwaitility { | |
private FileCreator fileCreator = new FileCreator(); | |
private String fileName = "file.txt"; | |
@Before | |
public void setUp() throws IOException { | |
Files.deleteIfExists(Paths.get(fileName)); | |
} | |
@Test | |
public void shouldCreateFile() throws Exception { | |
// when | |
fileCreator.createFile(fileName); | |
// then | |
await().until(fileIsCreated(fileName)); | |
} | |
private Callable<Boolean> fileIsCreated(final String fileName) { | |
return new Callable<Boolean>() { | |
@Override | |
public Boolean call() throws Exception { | |
Path path = Paths.get(fileName); | |
return Files.exists(path); | |
} | |
}; | |
} | |
} |
In a very expressive way, we achieve the same result. Timeout, polling delay and polling interval are of course configurable.
No comments:
Post a Comment