I will try to give some pointers.
1) Here I would not mock the whole logic to check the presence of the file as it makes part of the behavior of the method under test.
What you want to mock is the part where you actually try to retrieve the file, that is as you invoke getFilefromLocation()
.
2) Using a short delay between retries is a idea but is it enough to make your test robust?
Not really because you also have to assert that Thread.sleep()
was invoked and with the expected value. Not checking that in the unit test means that anybody can remove Thread.sleep()
in the getFile()
method and the test will still pass. You really don't want that.
Note that you cannot easily mock it : it is static
. You should move this statement into a DelayService
that you could mock.
3) Actually you parameterize retryCount
. As you write unit tests, you want to validate the behavior of the components according to your requirements. So you should also ensure that the class that depends on getFile(int retryCount)
passes effectively the expected parameter : 3
.
Besides if you don't want to allow more than a certain retry number, you also have to add this check in the method and assert this rule in the unit test.
According to the two first points, your actual code could be refactored as :
private FileLocator fileLocator; // added dependency
private DelayService delayService; // added dependency
// constructor to set these dependencies
public MyService(FileLocator fileLocator, DelayService delayService){
...
}
File getFile(int retryCount){
File file;
file = fileLocator.getFilefromLocation(); // -> change here
if(file!=null) return file
if(file==null and retryCount>0){
delayService.waitFor(); // -> other change here
--retryCount;
File filePicked = getFile(retryCount)
}
else return null;
}
About the unit test part, you could use Mockito for mocking and mix "simple" unit tests with parameterized tests as some scenarios have a similar behavior with as variance the number of actual retries.
For example retrying 0, 1, 2 and 3 times and finding the file are 4 cases that you can parameterize.
And inversely failing to find the file doesn't need to be parameterized as all retries will should be done to validate the behavior.
Here is a code example relying on JUnit5 and Mockito (not tested) that you could adapt according to your actual code. I just illustrated with the parameterized test. The other test cases should not be more complex to implement.
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.jupiter.api.Assertions;
import org.mockito.Mockito;
import org.mockito.Mock;
private static final long EXPECTED_DELAY_MN = 1;
private static final long RETRY_COUNT = 3;
@Mock
FileLocator fileLocatorMock;
@Mock
private DelayService delayServiceMock;
MyServiceTest myServiceTest;
public MyServiceTest(){
myServiceTest = new MyServiceTest(fileLocatorMock, delayServiceMock);
}
@ParameterizedTest
@ValueSource(ints = { 0, 1, 2, 3 })
public void getFileThatFailsMultipleTimeAndSuccess(int nbRetryRequired){
// failing find
for (int i=0; i < nbRetryRequired; i++) {
Mockito.when(fileLocatorMock.getFilefromLocation(...)).thenReturn(null);
}
// successful find
File fileByMock = new File(...); //fake file
Mockito.when(fileLocatorMock.getFilefromLocation(...)).thenReturn(fileByock);
File actualFile = myServiceTest.getFile(RETRY_COUNT);
// assertions
Mockito.verify(delayServiceMock, Mockito.times(nbRetryRequired))
.waitFor();
Assert.assertSame(fileByMock, actualFile);
}
}