I am pondering over a few different ways of writing utility classes/functions. By utility I mean a part of code being reused in many places in the project. For example a set of formatting functions for the date & time handling.
I've got Java background, where there was a tendency to write
class UtilsXyz {
public static doSth(){...};
public static doSthElse(){...};
}
which I find hard to unit test because of their static nature. The other way is to inject here and there utility classes without static members.
In Dart you can use both attitudes, but I find other techniques more idiomatic:
- mixins
Widely used and recommended in many articles for utility functions. But I find their nature to be a solution to infamous diamond problem rather than utility classes. And they're not very readable. Although I can imagine more focused utility functions, which pertain only Widgets, or only Presenters, only UseCases etc. They seem to be natural then.
- extension functions
It's somehow natural to write '2023-01-29'.formatNicely(), but I'd like to be able to mock utility function, and you cannot mock extension functions.
- global functions
Last not least, so far I find them the most natural (in terms of idiomatic Dart) way of providing utilities. I can unit test them, they're widely accessible, and doesn't look weird like mixins. I can also import them with as keyword to give some input for a reader where currently used function actually come from.
Does anybody have some experience with the best practices for utilities and is willing to share them? Am I missing something?
To write utility functions in an idiomatic way for Dart, your options are either extension methods or global functions.
You can see that they have a linter rule quoting this problem:
Extension methods.
I did not find any resource that points that the extension methods are static, neither in StackOverflow or the Dart extension documentation. Although extension can have static methods themselves. Also, there is an open issue about supporting static extension members.
So, I think extensions are testable as well.
To test extension methods you have 2 options:
EDIT: as jamesdlin mentioned, the extension themselves can be tested but they cannot be mocked since they need to be resolved in compile time.
Global functions.
To test global functions, just import and test it.
I think the global functions are pretty straightforward:
That's why I prefer this approach for utility functions that are not attached to any other classes.
And, if you are writing a library/package, the annotation
@visibleForTestingmay fall helpful for you (This annotation is from https://pub.dev/packages/meta).