How to test a method which creates directories and file system - java

This is my method, which creates a file system when gets called.
/**
* Process all messages from {#code messagesDir} directory and save results in {#code resultsDir}
*
* #param messagesDir messages directory
* #param resultsDir results directory
* #param logDirectory directory where should be written logs
* #param cleanupResultDir whether cleanup result directory
* #throws PtfProcessorException if provided {#code messagesDir} is invalid directory or there was comparision exception
*/
public void processMessages(String messagesDir, String resultsDir, String logDirectory, boolean cleanupResultDir)
throws PtfProcessorException {
// check if the directories are valid
checkIsValidDirectory(messagesDir);
checkIsValidDirectory(resultsDir);
// get operations directories
final File[] operationsDirsFiles = getInnerDirs(messagesDir);
// get results directory and clean up it if cleanupResultDirectory is true
final File resultsDirFile = new File(resultsDir);
if (cleanupResultDir) {
cleanUpDirectory(resultsDirFile);
}
// compare messages by operation bases
for (File operationDirFile : operationsDirsFiles) {
// operation log directory file
final File logOperationDirFile = new File(logDirectory, operationDirFile.getName());
try {
compareOperationMessages(operationDirFile, resultsDirFile, logOperationDirFile);
} catch (PtfProcessorException e) {
writeExceptionLog("Exception while comparing operation: " + operationDirFile.getName(),
e, logOperationDirFile);
}
}
}
Now I must write a unit test for this method. I have read several posts related to "TemporaryFolder" in Mockito and "Rule" annotation, but to tell the the truth, I do not know which approach I need. Anybody please help me to find a way for testing this method.
If there is a necessity for providing the helper methods used in aforementioned method, I can provide them.

One of the aproaches may be to create wrapper class for File and then provide it into the class that this method resides. Then you will be able to mock it with mockito and verify whether proper methods were invoked.
Normally you should not test classes that are not in your project, (File for example). This is not a good practice while writing unit tests. The thing that you can test is behavior of your method, that is whether proper external methods were invoked.
You should also remember to check if the validation is working by providing various inputs in your unit tests.

I suggest to use JUnit's TemporaryFolder rule.
public class YourTest {
#Rule
public final TemporaryFolder folder = new TemporaryFolder();
#Test
public void verifySomeFileBehaviour() throws Exception {
File messagesDir = folder.newFolder();
File reportsDir = folder.newFolder();
File logsDir = folder.newFolder();
//create your object and then
yourObject.processMessages(
messagesDir.getAbsolutePath(),
reportsDir.getAbsolutePath(),
logsDir.getAbsolutePath(),
true
);
//now you can verify the contents. E.g.
assertTrue( new File( messagesDir, "firstMessage.txt" ).exists() );
}
}
I recommend to use AssertJ as assertion library because it has nice assertions for files.

The real issue here: you created hard to test code - because you are implementing more than one responsibility in this method. Instead of collecting file names, and then creating them, your method could for example look like this:
public void processMessages(String messagesDir, String resultsDir, String logDirectory, boolean cleanupResultDir)
throws PtfProcessorException {
directoryValidator.validate(messagesDir, resultsDir);
final File[] operationsDirsFiles = getInnerDirs(messagesDir);
cleanupService.cleanup(cleanupResultDir, resultsDirFile);
fileService.create(operationsDirsFiles)
The above is merely meant as "pseudo code" to give you some ideas. The core point is: by extracting behavior into its own class/interface you enable yourself to test each functionality separately. And later on, you can use dependency injection to make that functionality available to more complex methods. You see - you could now mock all those "service" objects; and all of a sudden you only talk about verifying that you pass a certain list of File objects to another method.
Long story short: as usual the answer is: write testable code. Follow clean code rules, such as single responsibility principle. And when you do that, the quality of your production code will improve; and your tests will be much simpler to write down. Without the need to do fake file system operation all over the place.

Related

Prevent special class from static mocking

I have a class named SpecialClass. This class has the method doSomething in it. This class is in this particular manner special, that I do not want to mock this class at all via MockedStatic. It is very easy for me to remeber that I don't want to mock this class, but there are other people programming at the same project and they should know to not mock this class but I can not relate on it.
Given this special test:
#Test
void testSomething(){
try(MockedStatic<SpecialClass> mock = Mockito.mockStatic(SpecialClass.class)){
mock.when(SpecialClass::doSomething).thenReturn(0);
System.out.println(SpecialClass.doSomething());
}
}
I dont want to let this test execute at all. But it will execute, because Mockito does not know about my SpecialClass and not to mock it. There could be also various other locations where SpecialClass could be mocked.
Some classes can not be mocked because of Mockito decides so. Given this mock of java.lang.System:
#Test
void testFail(){
try(MockedStatic<System> sys = Mockito.mockStatic(System.class)){
// some code
}
}
I get the exception org.mockito.exceptions.base.MockitoException: It is not possible to mock static methods of java.lang.System to avoid interfering with class loading what leads to infinite loops, which is ok.
My question is: Can I somehow have some global project settings (or some annoations) to mark my class SpecialClass to be not used in Mockitos MockedStatic? I would be also ok if I can also not mock this class in a normal way.
I tried to find some information regarding restricting mocks, but i was unable to find some.
My mockito version of mockito-core and mockito-inline is 4.3.1
I used a junit test to read all my files and trying to detect all mocks.
Please note that my test is working in a very special assumption that all of my test classes are in the same directory. This will hoever not be given in every project. In this case you will need to find the files recursivly.
class NoStaticMockTest {
#Test
void testNoMocks() throws IOException {
// Base dir of my tests
File testDir = new File("src/test/java");
// reading all Files in the test directory excluding this file
for (File file : testDir.listFiles(pathname -> !pathname.getAbsolutePath().endsWith("NoStaticMockTest.java") && pathname.isFile())) {
try (FileInputStream fis = new FileInputStream(file)) {
// Reading the contennt and trying to find MockedStatic<SpecialClass>
String content = IOUtils.toString(fis);
if (content.contains("MockedStatic<SpecialClass>")) {
Assertions.fail(file.getAbsolutePath() + " used MockedStatic<SpecialClass> which is not allowed");
}
}
}
}
}

Specific setup for each junit test

I have a sequence of tests which have to be fed an input data in the form of a file.However,the exact data content to be fed into each would be specific.
I intend to use temporary files to achieve this.
The Setup method does not take a parameter.
SO ,what could be done so that the setup can be made to read a specific fragment for each specific test.
The actual set of steps in Setup would be same - creating a temporary file,but with a specific tailored piece of data.
Setup methods (i.e., methods annotated with #Before) are designed for running the same steps before every test case. If this isn't the behavior you need, just don't use them.
At the end of the day, a JUnit test is just Java - you could just have a method that takes a parameter and sets up the test accordingly and call it explicitly with the different arguments you need:
public class MyTest {
private void init(String fileName) {
// Reads data from the file and sets up the test
}
#Test
public testSomething() {
init("/path/to/some/file");
// Perform the test and assert the result
}
#Test
public testSomethingElse() {
init("/path/to/another/file");
// Perform the test and assert the result
}
}

jUnit4.0 : how to know current test method name?

I've implemented a feature in my jUnit tests that takes, for every test case, a fresh copy of a data source. This copy is taken in a folder specific for each test case. The idea is that every test case can start from a clean situation, manipulate it and let it as such after the run. This is often useful when the test fails for analysing the problem.
For now I have to call this feature directly in the test method because I don't know how to retrieve the current test name:
public void testTest1() {
TestHelper th=TestHelper.create("testTest1",subPathToDataSource);
// do the test...
Path dataPath = th.getDataPath();
...
}
I would like to be able to write something like this:
Path dataPath;
#Before
public initTest() {
th=TestHelper.create(SomeJUnitObject.getCurrentTestName(),subPathToDataSource);
...
}
public void testTest1() {
// do the test...
Path dataPath = th.getDataPath();
...
}
Until now I found as answers : "You don't need to know that"... But I do need it !
Is this possible ?
Kind regards
Look at the TestName rule.
You should be able to add in your test class:
#Rule TestName name=new TestName();
And then access it.
(On phone, so can't check versions support/details - might be 4.x only)
Here is an alternative approach; create an abstract class which your "real" test classes inherit.
I have several such examples in my projects and here I will give one, mainly testing for individual JSON Patch operations.
All my test files are JSON, and located under an appropriately named resource directory. The base, abstract class is JsonPatchOperationTest. And here is the full code of AddOperationTest which tests for JSON Patch's add operation:
public final class AddOperationTest
extends JsonPatchOperationTest
{
public AddOperationTest()
throws IOException
{
super("add");
}
}
And that's it! Not even one test method in this class, but of course your implementation may vary.
In your case you probably want to pass the directory name as a constructor argument, or the like.

Simulate File in Java

I'm trying to write unit tests for a method that takes a String filename, then opens the file and reads from it. So, to test that method, I thought about writing a file, then calling my method. However, in the build farm, it is not possible to write files arbitrarily to disk. Is there a standard way to "simulate" having a real file in my unit test?
I've found that Mockito and Powermock are a good combination for this. Actually there's a blog post with a sample, where the File-class's constructor is mocked for testing purposes. Here's also a small example I threw together:
public class ClassToTest
{
public void openFile(String fileName)
{
File f = new File(fileName);
if(!f.exists())
{
throw new RuntimeException("File not found!");
}
}
}
Testing with Mockito + Powermock:
#RunWith(PowerMockRunner.class)
#PrepareForTest(ClassToTest.class)
public class FileTest
{
#Test
public void testFile() throws Exception
{
//Set up a mocked File-object
File mockedFile = Mockito.mock(File.class);
Mockito.when(mockedFile.exists()).thenReturn(true);
//Trap constructor calls to return the mocked File-object
PowerMockito.whenNew(File.class).withParameterTypes(String.class).withArguments(Matchers.anyString()).thenReturn(mockedFile);
//Do the test
ClassToTest classToTest = new ClassToTest();
classToTest.openFile("testfile.txt");
//Verify that the File was created and the exists-method of the mock was called
PowerMockito.verifyNew(File.class).withArguments("testfile.txt");
Mockito.verify(mockedFile).exists();
}
}
If you use JUnit, there is the TemporaryFolder. Files are deleted after the test. Example given on the page:
public static class HasTempFolder {
#Rule
public TemporaryFolder folder= new TemporaryFolder();
#Test
public void testUsingTempFolder() throws IOException {
File createdFile= folder.newFile("myfile.txt");
File createdFolder= folder.newFolder("subfolder");
// ...
}
}
However, I have also used it for testing my Android class read/write capabilities like:
[...]
pw = new PrintWriter(folder.getAbsolutePath() + '/' + filename);
pw.println(data);
How about using mocked stream classes, which override the real ones (like BufferredReader, File) completely (meaning all methods or all methods you use)? The data can be saved as an array of bytes, for example, in some singleton, if they are to be used between different test classes.
This is highly frowned upon:
The smallest amount of testable code. Often a single method/function,
sans the use of other methods or classes. Fast! Thousands of unit
tests can run in ten seconds or less! A unit test NEVER uses:
a database
an app server (or server of any kind)
file/network I/O or file system;
another application;
the console (System.out, System.err, etc.)
logging
most other classes (exceptions include DTO's, String, Integer, mocks and maybe a few others). "
Source
If you must read from a file, have a test file pre-generated that all unit tests read from. There is no need to write anything to disk.

Junit test that creates other tests

Normally I would have one junit test that shows up in my integration server of choice as one test that passes or fails (in this case I use teamcity). What I need for this specific test is the ability to loop through a directory structure testing that our data files can all be parsed without throwing an exception.
Because we have 30,000+ files that that 1-5 seconds each to parse this test will be run in its own suite. The problem is that I need a way to have one piece of code run as one junit test per file so that if 12 files out of 30,000 files fail I can see which 12 failed not just that one failed, threw a runtimeexception and stopped the test.
I realize that this is not a true "unit" test way of doing things but this simulation is very important to make sure that our content providers are kept in check and do not check in invalid files.
Any suggestions?
I think what you want is parameterized tests. It's available if you're using JUnit4 (or TestNG). Since you mention JUnit, you'll want to look at the #RunWith(Parameterized.class)
and #Parameters annotations' documentation.
I'd write one test that read all the files, either in a loop or some other means, and collected all the failed files in a collection of some kind for reporting.
Maybe a better solution would be a TestNG test with a DataProvider to pass along the list of file paths to read. TestNG will create and run one test for each file path parameter passed in.
A Junit3 answer: Create a TestSuite, that creates the instances of the TestCases that you need, with each TestCase initialized according to your dynamic data. The suite will run as a whole within a single JVM instance, but the individual TestCases are independent of each other (setUp, tearDown get called, the error handling is correct, reporting gives what you asked for, etc).
The actual implementation can be a bit clumsy, because TestCase conflates the Name of the test with the METHOD to be run, but that can be worked around.
We normally just combine the suite with the dynamic testcases in the same class, and use the suite() method to get the TestSuite. Ant's JUnit task is smart enough to notice this, for example.
public class DynamicTest extends TestCase {
String filename ;
public DynamicTest ( String crntFile ) {
super("testMethod");
filename = crntFile ;
}
// This is gross, but necessary if you want to be able to
// distinguish which test failed - otherwise they all share
// the name DynamicTest.testMethod.
public String getName() {
return this.getClass().getName() + " : " + filename ;
}
// Here's the actual test
public void testMethod() {
File f = new File( filename ) ;
assertTrue( f.exists() ) ;
}
// Here's the magic
public static TestSuite suite() {
TestSuite s = new TestSuite() ;
for ( String crntFile : getListOfFiles() ) {
s.addTest( new DynamicTest(crntFile ) ) ;
}
return s ;
}
}
You can, of course, separate the TestSuite from the TestCase if you prefer. The TestCase doesn't hold up well stand alone, though, so you'll need to have some care with your naming conventions if your tests are being auto-detected.

Categories