4

The average utility class: A stateless class that provides some functionality by exposing static methods. Its default constructor is private to avoid instantiation. When ever the average utility class is used, a hard dependency is created.

So, referencing the utility class is the way to invoke a desired method. But couldn't we replace this class reference by a "dummy object" and remove the hard dependency? Let me show you what I mean(examples are in Java). Be aware, I tried to make things as simple/short as possible!

This is an average utility class, functionality is wrapped in static methods:

public class SomeUtilClass {
    // to avoid instantiation
    private SomeUtilClass(){
    }

    public static Object someUtil(){
        return null;
    }
}

Instances of the following class will serve as a "dummy objects" in order to call someUtil. It's supposed to provide the same functionality as the SomeUtilClass does but functionality is wrapped in non-static methods.

public class SomeUtilClassButOOP {
    // to create "dummy objects"
    public SomeUtilClassButOOP(){
    }

    public Object someUtil(){
        return null;
    }
}

Here, both attempts in comparison:

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class BringThemBothTogether {

    @Test
    public void test(){
        // the way it's usually done
        Object thisIsBrilliant = SomeUtilClass.someUtil();
        // what crossed my mind
        Object butILikeThis    = new SomeUtilClassButOOP().someUtil();

        assertEquals(thisIsBrilliant, butILikeThis);
    }
}

The downside of referencing SomeUtilClass is the hard dependency, infamous for making Tests harder than they need to be. All of the above was done in an attempt to get rid of hard dependencies but still use utility classes.

Well, does this attempt work out? Where is the pitfall?

I would be happy, if your shared your thoughts and ideas with me :)

2
  • Is there a reason you're not using a static class for SomeUtilClass?
    – Flater
    Commented May 17, 2021 at 8:06
  • Java classes can't be static unless they are nested classes. But static utility classes are used in C# if that's what you are thinking of ...
    – Diggi55
    Commented May 17, 2021 at 8:39

3 Answers 3

13

Static utility classes do not necessarily make testing harder. If the methods of those utility classes are pure functions (they have no side effects) then there is no need to mock the utility functions in a unit test. Just let the System Under Test call the real utility function and assert the output.

If, however, the utility function has side effects (it works with files, a database, or other resource that exists outside the current process) then mocking the utility methods is desirable when writing unit tests. This is the line between "it's OK" and "it's not OK" to use static utility methods.

The decision to instantiate a "utility class" or use static methods is determined by whether or not those methods have side effects. If they do, then it would be advisable to define an interface for the util class, have the util class implement that interface, and inject that utility object to all other objects that depend on it. Now you can properly isolate those other objects for unit testing.

Utility classes that only contain pure functions can be unit tested easily even if all methods are static.

If the utility class needs to use a resource that runs in a different process (file system, web service, database, etc) then you cannot write unit tests for that object. You would be writing integration tests to validate the utility object's behavior against that out-of-process resource.

2
  • 1
    In principal I agree but the utility methods must not extend a certain complexity! Otherwise your unit tests of units using it always have deal with that complexity and maybe testing it over and over again! Commented May 14, 2021 at 16:34
  • 1
    @ArneBurmeister: definitely agreed. Even more to that point, complex utility methods may indicate you are missing a class or abstraction. It could be a design smell worth investigating. Commented May 14, 2021 at 16:39
3

Nothing substantial has changed. You still have a hard dependency on that particular class, though now you've added a bit of extra indirection.

And this is OK. Not everything has to be modelled in an OOP manner. It is perfectly fine to have static methods and to have hard dependencies. Java forces us to express such non-OOP concepts in an awkward manner (classes with private constructors) but it can still be a legitimate part of a software design. Java also has static imports that can make such utilities more convenient.

There are of course some problems with utility modules like this.

First, the word “utility” is sometimes used as an excuse to aggregate various unrelated functionality. Here, it can be useful to consider what these utilities relate to. Shouldn't they be part of another class? Are there different areas of utilities that should be kept separate? For example, the Java standard library java.util module doesn't have a particularly good name because it's actually the collections framework. But java.lang.Math is a great and reasonably scoped utility “class”, as is java.util.Arrays.

Next, you mention a legitimate concern with testability. Software does not become untestable because it has static dependencies on something. That just means those dependencies cannot be mocked during a test. Sometimes this is a problem, sometimes not. In particular, “pure functions” that don't change anything aren't an issue – like math functions. I/O functions can be more of a problem, but this could indicate you don't want functions like static String readFile(String name) but a DataSource interface that can be injected properly. Making a utility class extensible and injectable doesn't really help – you need a proper design instead.

Even when you depend directly on static functions, that doesn't have to make tests impossible: you don't have to test everything in isolation. If I have a function f() that depends on some function d(), then I can test d() and ensure that it works as expected. When it comes to testing f(), it doesn't really matter whether I keep the dependency on d() (which is known to work) or mock it out. The purpose of automated tests is to reproducibly demonstrate business value, not to tediously dissect the software. So the decision whether to mock or not should largely be a question of convenience when writing the test, though at some point the speed of the entire test suite also becomes a factor.

Dependency injection becomes very important when you have complex business logic. First, this logic might vary (over time, or between deployments), so you want to separate the things that stay the same from the things that vary. But pretty much by definition, utilities do not vary like this. If they do, that should be modelled explicitly with some interface. Secondly, this business logic might be too complex to test in its entirety – it's necessary to extract individual rules and aspects that can be tested in isolation. Being able to mock other dependencies is great – but simple utilities are not part of this kind of business logic that needs to be mocked away.

So in conclusion, think about your actual needs. Maybe you benefit from this kind of functionality, but most likely not. Instead, keep it simple. But it may be worth reconsidering whether some functionality should be stuffed into a general-purpose utility class, or whether it needs to be modelled explicitly as part of your design.

0

Utility classes are anti-pattern to an extent. Most of the time I have see people putting methods in there that belong to a specific object's behavior. When I see a class or package named Utility, It is a code smell for me. Indicator of poorly designed objects. Take Phone number and SocialSecurityNumber for example, some developer would come up with this string formatting method that given a string and special character it will return a formatted string so they can resuse same code. While in reality there should be an objects called PhoneNumber and SocialSecurityNumber that has a behavior like getFormatterPhone() or getFormattedSSN(). This behavior should be testable and should not be a static method in Utility class.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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