Search code examples
javaunit-testingarchunit

Checking parameters of all method calls


I work on a Java app which uses a ResourceBundle with a static helper class.

Simplified it works like this:

class Bundle()
{
  static String get(key)
  {
    resourceBundle.get(key)
  } 
}

uiClass()
{
  new Label(Bundle.get("key.to.ressource"))
}

Now if you insert a wrong Key (e.g. wrongly capitalized "Key.To.Resource") the app only throws an Exception at runtime and don't fail while compiling or testing.

So I wanted to write an (Arch-)UnitTest which simply checks all method calls to Bundle.get() in the project with all used parameters and check for exceptions. Does ArchUnit even support such tests? If not, how could I achieve the same result: Ensure that all keys are valid while compiling or testing?

I tried using ArchUnit (via Maven: com.tngtech.archunit:archunit:1.2.1) but I'm not sure, if these more dynamic values are supported.


Solution

  • NB: Please see editorial note1 about the nature of this answer.

    Requires special tooling

    Basic java can't do this, for obvious reasons: Java does not require, nor can you write java source that makes javac require, that any calls to Bundle.get are string literals. And if they aren't string literals, how can you analyse whether the code as written contains typoes?

    Hence, you need more tools than just "javac and a bunch of libraries". You need compiler plugins, or code analysers.

    The upside is, plenty will do - you can use any library that is capable of parsing and understanding java source code, hunt for any calls to Bundle.get, check if the expression passed as only parameter is a string literal or a near-string literal (i.e. something like private static final String MY_RESOURCE_KEY = "foo.bar"; up top and in the code Bundle.get(MY_RESOURCE_KEY) - that's near enough to a string literal that such a tool should, and can, figure it out).

    Alternatively, you can use a bytecode inspector, such as archunit, because finding static calls where the first param is straight from the string constant pool is similarly doable. Even easy.

    However, those tools aren't just part of javac itself and not particularly common amongst build stacks. They exist - archunit is one of them. pmd is another. CheckStyle can probably do something here.

    However, the idea then would be that you catch these issues at build time. So, that's still not quite as fast as it could have been: You write your code with no idea you made a typo, then you run the build, and the build script tells you about a bug. Which is possibly better than 'your unit tests', but maybe not - (proper setups, such as with eclipse java builders, means you can run tests much, much faster and more often than running the entire build). It certainly beats finding out you typoed a resource key in production, of course!

    But, it'd be even better if typoing a resource key results in a near instant red wavy underline in whatever editor you are using, and the best if you can't typo that resource key because your IDE actively gives you a menu of resource keys to choose from.

    That best world is something you're already very aware of: IDE auto-completers are exactly like that. You can't typo toLowerCase() because the IDE will tell you immediately that toLowerCasde() doesn't exist. Better yet, you're unlikely to make that typo if you use autocomplete. You type .tLC, hit ctrl+space, hit enter, and voila.

    Thus, the real answer

    Don't do it the way you want. Don't use string literals at all!

    Instead, you want a single java file that contains a boatload of:

    public static final String APP_TITLE = "app.title";
    

    And star-static import this file into your projects. Then, you write Bundle.get(APP_TITLE) and you get this 'best' setup: Your IDE will instantly tell you if you typo APP_TITLE, and, you can even use auto-complete to get a list of available resource keys.

    This then has transformed your question into something slightly different:

    How do I make that file?

    You don't want to manually maintain this file, and it would just kick the can down the road: Now how do you detect typoed resource keys in this file?

    The answer to that is simple: Don't maintain it. Don't write it. Instead, generate it.

    That's not hard to do. Given a resource bundle definition file you can write some software, in java if you want, that emits a source file.

    If you want, you can then even package this application into an annotation processor, so that any build will automatically pick it up and produce the ResourceKeys source file. Edit the resource bundle file, save it, and any build will first produce a new version of ResourceKeys.java and only then will javac continue compiling the rest, against this newly produced ResourceKeys. IDEs (including eclipse's internal fast java builders) are capable of running annotation processors and thus, you get the experience of editing your resource bundle definition, saving it, and instantly getting entries in your 'errors in your source files view' about refs to resource keys that just disappeared. You can switch to a java editor view and the resource key you just wrote is available immediately in auto-complete dialogs and such.

    Explaining precisely what to write is a bit complicated - there are plenty of tutorials available and easy to find via a web search about how to write annotation processors.

    NB: You might be hesitant, as this complicates your build. But.. you already signed up for that! You can't do this without some additional non-default modifications to the basic build process. If you're going to go through the trouble of injecting an additional linter into the build, why not get a nicer developer experience and inject an annotation processor or other source code generation step instead?


    [1] I'm on the edge of writing an answer to a different question here. However, it's far too long for a comment and would precisely, and effectively, solve OPs problem - it's useful to anybody asking this specific question. If I have misjudged the situation and this is not an appropriate answer, let me know, and I'll delete it, or post a different question and move the answer over.