Core Java

Unit Testing: Java AND JavaScript?

You know that thing where recruiters think that Java and JavaScript are probably the same thing? Well, they’re really not… unless…

Sometimes when working with a Java project, you sail too close to also depending on JavaScript. In these cases, the unit testing can get quite messy. There are few ways in which it doesn’t get messy.

There are a few workarounds for this, but it gets especially nasty when the JavaScript that the project uses is intended to be run in the JVM, in which case it’s neither Java nor JavaScript and can only be unit tested in a specific and limited way.

It can be done.

Unless it can’t.

When do Java and JavaScript Combine?

There are two key use cases, and they’re quite different.

  • We’re using server-side rendered pages and those pages contain JavaScript
  • We have a JavaScript script engine, such as Nashorn or Rhino, to run some dynamic code using JavaScript as the scripting language

There is an alternative use case which I think is a very different animal.

  • We use a framework like Angular, React or Vue to build an SPA to sit on top of a Java set of APIs

In this latter case, while everything may be in the same repo (or not), we’ve fundamentally got a transpiling, packaging process, and a place where pure JavaScript (or TypeScript) unit tests can and should be created, and it’s genuinely independent of the Java build.

In the other cases, it’s a deeply grey area.

How Do I Know?

I’ve solved the above problems in real projects. I even contributed to HtmlUnit, which I used as part of solving the JavaScript in web pages problem. I attempted to submit a fix to Mocha to help me create a Java/JavaScript unit test framework, but that didn’t work out in the end.

The point is. I’ve learned some of this stuff from bitter experience.

It’s kind of not meant to be, yet it still happens.

JavaScript in Java Server-side-rendered Web Pages

Before we had a cleaner separation of front end into SPAs, we might use a Java server to render the HTML of the page on each request to the server. We still can. I did a project this way last year.

The request comes to the Java server and the server then executes a template to render the right HTML, including some .js files and possibly adding some inline JavaScript, as necessary. The template used to be Velocity or JSP, but in modern Spring applications might normally be Thymeleaf. They all work in similar ways in practice.

There are a couple of challenges:

  • Without a transpiling framework for the JavaScript that appears inline on the page – bindings of controls or whatnot – then we have to assume the worst in terms of the runtime capabilities of JS on the target browser
  • Sometimes we want to write a test that says “when the data’s like this, then the page does that” and those tests need the JavaScript to run as well as the server side rendering

It’s worth pointing out that the unit tests to see that the page really has the right stuff on it most often depend on waiting for the DOM to be finally rendered after the JavaScript ran. In practice this can take seconds. The fix I submitted to HtmlUnit was to help speed this up as much as possible. My tests still ran slow.

JavaScript Language Level

JavaScript is a real mess of a language. Depending on the runtime there are two dimensions of things we may not have:

  • Keywords and constructs – e.g. const or =>
  • Functions – i.e. some library functions aren’t available on all browsers/runtimes, requiring us to add polyfills to replace them

While the latest node runtime, which we might use for unit testing will have EVERYTHING, the target browser might not. So we somehow need to test in the lowest-common denominator runtime.

Similarly, when running JavaScript in the JVM, we cannot just presume that the dynamic scripting engine will support everything that the JavaScript language allows. We can assume a good 90%+, but we want to find 100% of issues before ever deploying our software, so unit testing of JavaScript for the JVM runtime needs to be tested in that runtime.

Running Server Side JavaScript in Java

Even the title makes this seem like a bad idea. However, it’s not entirely a bad thing. In general we do it so that:

  • We get the ability to deploy dynamic scripts
  • We can mix Java objects and functionality from the server’s context with our scripts

As such, the general case for testing JavaScript that runs in the JVM is something like this:

  • Create all the Java context that the script consumes
  • Ensure all libraries that the script imports are on the classpath
  • Execute the script from a unit test
  • Assert the output of the script in Java at the end of the execution

This presumes that all of the above is possible. It’s generally possible, with software we wrote. I’ve done it.

One assumption might be that the script returns an assertable value. Maybe it does. Often they don’t just do that. Some scripts also act on the context, and we need to assert what happened to the outside world. In those cases, we have to spy or mock that outside world before running the script. Easier with our own code, harder with someone else’s.

In other words, it’s somewhere from moderately to very not possible.

Can’t You Just Unit Test the Pure JavaScript?

So what if we said to ourselves, in both of the above scenarios, that we wanted to have some JavaScript libraries that were pure and isolated from the messy implementation of the outside world, and could be unit tested in their own right?

The simple answer is sure we can. But. This assumes that it’s possible and desirable for such a library to be imported into the target run time.

  • For a web page, it becomes another static file to import from the JavaScript resources – i.e. more page load requests – that might be ok, it might incur PageSpeed penalties or even slow down some other types of test automation
  • For a dynamic script to run in a JVM process, it presumes you can have additional library files, rather than a monolithic dynamic script (spoilers, it’s usually only the latter)

In those instances, the only problem left to solve is the language level project. Let’s be clear, though. When I’ve used lodash or jquery in a web page that’s rendered from Java, I’ve depended on their own unit testing and transpilation.

However, when I’m trying to test in-page control bindings, or when I’m trying to test a scriptlet that runs within a Java low-code process, I don’t necessarily have the ability to “just import a well tested library”.

Oh… and then there was that time that I could use a well tested library, and there was another problem…

Java Objects Don’t Behave Like JavaScript Objects

So, even when you can have a pure JavaScript test on a pure JavaScript function that you can import into your running JVM-based JavaScript runtime, you can’t 100% trust it.

It turns out that if that script is going to run on objects that originated in Java, then there are cases (largely around detecting the type of object you’re interacting with) where the runtime behaves differently with Java-originated objects, compared with JavaScript-originated objects.

In other words, you can do a pure JavaScript test (I’ve done it) but you ALSO have to retest using Java and the scripting engine (I’ve done that too), and you’ll get slightly different edge cases.

It’s a Trap?

This whole subject is easy in theory, and tricky in practice.

It’s not a trap.

It’s just really really nuanced.

The best unit tests are precise, fast-running, and relevant. This means they need to run in the same way (or as near as damn-it) to real runtime, while still allowing us to slice away external dependencies.

JavaScript is quite a vague language, so you need to specialise the runtime of the test to the precise runtime it’ll be used with. Java-hosted JavaScript, intended to interact with Java objects and libraries, is neither Java nor JavaScript.

In many cases, testing Java-hosted JavaScript is a case of sucking up the fact that it must be performed as an integration test.

In many cases, testing Java server-side-rendered pages is a case of tolerating the long delays caused by waiting for the DOM in HtmlUnit, or even a real browser, to actually render the page and then probing it.

There are occasional easy wins within this subject, but they cannot often be 100% trusted.

Good luck!

Published on Java Code Geeks with permission by Ashley Frieze, partner at our JCG program. See the original article here: Unit Testing: Java AND JavaScript?

Opinions expressed by Java Code Geeks contributors are their own.

Ashley Frieze

Software developer, stand-up comedian, musician, writer, jolly big cheer-monkey, skeptical thinker, Doctor Who fan, lover of fine sounds
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button