4

I found that it is so hard to test classes that depend on other utility classes as java.nio.file.Files. It is also impossible to mock them using the classic unit testing stack (junit,mockito,..) unless you use other heavy mocking frameworks like powermock which i consider a bad practice in case of building projects from scratch and also a nightmare if you try to integrate it with maven plugins ( surefire, jacoco ,offline instrumentation,...)

So what i've decided to do to ease my unit testing, is to wrap this utility classes inside a normal instantiable class and inject them to my main class as follows:

public class IOManager {
    private final FileSystemUtilsWrapper fileSystemUtilsWrapper;
    private final HttpUtilsWrapper httpUtilsWrapper;

    public IOManager() {
        this.fileSystemUtilsWrapper = new FileSystemUtilsWrapper();
        this.httpUtilsWrapper = new HttpUtilsWrapper();
    }

    public IOManager(
            FileSystemUtilsWrapper fileSystemUtilsWrapper, 
            HttpUtilsWrapper httpUtilsWrapper
    ) {
        if (fileSystemUtilsWrapper == null 
            || httpUtilsWrapper == null) {
            throw new NullPointerException("...");
        }

        this.fileSystemUtilsWrapper = fileSystemUtilsWrapper;
        this.httpUtilsWrapper = httpUtilsWrapper;
    }

    public boolean doIOOperations(String filePath, String url) throws IOException {
        if (fileSystemUtilsWrapper.isMyFileReachable(filePath) 
            && httpUtilsWrapper.isMyURLReachable(url)) {
            // do something and returns IO state
        }
        return false;
    }
}

/**
 * FileSystem utils wrapper
 */
class FileSystemUtilsWrapper {
    public boolean isMyFileReachable(String filePath) {
        Path path = Paths.get(filePath);
        return Files.exists(path) && Files.isWritable(path);
    }
}

/**
 * http utils wrapper
 */
class HttpUtilsWrapper {
    public boolean isMyURLReachable(String url) throws IOException {
        HttpURLConnection urlConnection = (HttpURLConnection) new URL(url)
            .openConnection()
        ;
        // i keep it simple
        return urlConnection.getResponseCode() == 200;
    }
}

I just keep it simple for the sake of getting a quick insight.

Note that i've also considered using directly utility classes inside my SUT and mocking the I/O resources (creating and deleting a dummy file and using wiremock to mock the http endpoint ). While it may seem the easiest path to take, IMO one shouldn't do so, because it is not unit testing anymore but integration tests.

I would appreciate hearing your opinion on my analysis. thanks in advance and Best Regards,

  • 1
    It sounds like you're relying too much on being "correct." There's no "correct" way to do these things, there is only the way that produces the greatest benefits for the least cost. – Robert Harvey Nov 10 '18 at 15:05
  • @RobertHarvey but separation of concerns ( integration from unit testing) isn't going to produce benefits in terms of code maintainability and also productivity (unit tests are faster than integration tests) ? isn't sometimes advisable to have more tests than the production code itself to prevent regression (note here we sacrifice the least code)? – isqo Nov 10 '18 at 15:23
  • separation of concerns ( integration from unit testing) -- Those two things don't have anything to do with each other. isn't going to produce benefits -- Why not? unit tests are faster than integration tests -- Debatable, but what does that have to do with anything? sometimes advisable to have more tests -- The quality of your tests is more important than the number of tests you have. – Robert Harvey Nov 10 '18 at 15:44
  • @RobertHarvey Debatable, but what does that have to do with anything? i've seen so many projects that depend more heavily on integration tests than unit tests for regression detection, and sometimes they sacrifice unit tests for integration tests (unit tests are useless to them) ,the example i give is so simple and limited to the question's scope, but anyway i understand your idea, the problem is if one starts to mix unit tests with integration, it would become unmaintainable, this why i think we should have both separated (in the ideal word, no deadlines,...). – isqo Nov 10 '18 at 16:12
  • 2
    Integration tests can be slower to execute, but they can also be faster to write, and they touch more components, which means you get more bang for your buck. Not saying that relying solely on integration tests is the best thing, but I do understand why that happens. – Robert Harvey Nov 10 '18 at 16:18
1

What I like here are the overridable default values. That's good. It avoids hard coding. I recommend it regardless of testing. And have done so before.

What I'm not sure I like is that IOManager uses the wrappers not only as default values but as types. Anything that means to replace the wrappers has to inherit from them. The wrappers are concrete, not abstract. Which means this is the start of a yo-yo problem.

A good principle that could guide you away from that problem is the Dependency Inversion Principle. It teaches that:

  • High-level modules should not depend on low-level modules. Both should depend on abstractions.
  • Abstractions should not depend on details. Details should depend on abstractions.

Which isn't to say you must use interfaces here. But it would be nice if at least there were abstract classes named FileSystemUtils and HttpUtils that IOManager can use as types. That way when I pick up your code and switch from new URL(url) to new SecureURL(url) by creating HttpsUtilsWrapper, I won't be forcing some poor maintenance coder to look at unused, overridden code.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.