RxJS | JUnit | Testing | Programming | Software Engineering | JavaScript Lessons from Java for Testing in React October 21, 2018
Neil Chaudhuri (He/Him)

Neil Chaudhuri (He/Him)

LinkedIn Neil Chaudhuri (He/Him)

Have you found that your code has a lot of bugs even though you’ve invested in maintaining 90% code coverage? Have you also found that your tests break so often that you don’t want to write any more?

I have. With multiple clients.

Part of the problem is code coverage is a misleading indicator of quality. Even worse, you are writing tests that don’t test anything except the implementation details of your code. That’s almost worse than writing no tests at all.

This has really hit me as I learn to write tests for React. My career has been built primarily on Java, Scala, Ruby, and Python—i.e. backend development. Over the years I have written a lot of JavaScript too, but we are taking it to the next level and enduring some growing pains as we migrate the front end of a Scala application we built for a client to React (with all the trimmings—Webpack, TypeScript, RxJS, Immutable.js, etc.). React is spearheaded by Facebook and fortified by a vibrant community, so I assumed a consensus on patterns and practices.

Nope!

How do you reuse code in React? React developers will endorse anything from mixins to higher-order components to render props.

How do you manage complex state in React? React developers will endorse anything from Flux to Redux to MobX to simply RxJS.

I’ve even seen things get contentious. We are approaching the point where magazines advise couples on first dates to avoid discussing politics, religion, and React state management preferences.

Testing in React is no different—no consensus on “best practices.” Luckily though my experience testing on the server offers insight that significantly clarifies things for me.

Kent Saves Us When Superman Can’t

You will find that one consistent voice of reason emerging from the cacophony of React advice is Kent C. Dodds (KCD), Paypal engineer and React expert. The Notorious KCD is the new ”King of All Media” with great content on his blog, at conferences, on podcasts, and all over social media.

I have found KCD’s insights particularly useful in the area of React testing, and I recently came across his commentary on “shallow rendering,” a popular testing paradigm in the React community. Like pretty much everything else in React, shallow rendering is controversial, and KCD did not mince words in expressing his disdain for it. His take is a corollary of his even more controversial take that most tests should be integration tests.

It turns out I agree with KCD on shallow rendering, but I come to the same conclusion via an entirely different rationale based on my experience with testing on the server. Please take a moment and read KCD’s posts so you can understand shallow rendering, his sample code, and his rationale as it relates to the distinction between unit and integration testing.

Take your time. I’ll wait.

Lessons from Java

As you see in his post, KCD’s example focuses on a HiddenMessage component that contains a Fade component that in turn contains a CSSTransition component. A test based on shallow rendering would essentially “turn off” Fade and CSSTransition, and KCD maintains that makes the test pointless because it yields both false positives and false negatives. It does nothing to confirm the quality of HiddenMessage. Instead, he argues “unit testing” that only renders the top-level component should be replaced with “integration testing” where the top-level component is rendered with Jest mocks of its internal components.

I completely agree with KCD that shallow rendering is useless but for a completely different reason. We can look to Java for insight. Here is an analogous implementation of HiddenMessage in Java.

public class HiddenMessage extends Component {
  private boolean show = false;
  private Fade fade;
  
  public HiddenMessage(Children children, Props props) {
    fade = new Fade(children, props);
  }
  
  public void toggle() {
    this.show = !this.show;
  }
  
  public String render() {
    return
      "<div>
        <button onClick={" + this.toggle() + ">Toggle</button>" + 
        fade.render() +
      "</div>";
  }
  
  public static class Fade extends Component {
    private CSSTransition cssTransition;
    
    public Fade(Children children, Props props) {
      cssTransition = new CssTransition(children, props, 1000, "fade");
    }
    
    public String render() {
      return cssTransition.render();
    } 
  }
  
  public static class CSSTransition extends Component {
    private Children children;
    private Props props;
    private int timeout;
    private String className;
    
    public CSSTransition(Children children, Props props, int timeout, String className) {
      this.children = children;
      this.props = props;
      this.timeout = timeout;
      this.className = className;
    }
    
    public String render() {
      return 
        "<CSSTransition" + " " +  props + " " + timeout + " " + className + ">" + children + "</CSSTransition>"
    }
  }
}

I would say this faithfully represents the relationship among the three components albeit with Java idioms. Fade and CSSTransition only exist within the context of HiddenMessage. Clients have no direct access to those internal components. The components themselves are therefore defined as private static classes within HiddenMessage; instances are marked private within HiddenMessage.

A quick aside: I recognize that this makes for unorthodox Java code, but the goal here is to reflect the intent of the React component HiddenMessage as accurately as possible in a “Java way.”

Now look at this and imagine a test for HiddenMessage where Fade and CSSTransition are disabled into no-ops. All you do is render a button that proves you can toggle a flag. Who cares?. There is so much more to HiddenMessage we never explore. Such a test offers no insight into whether HiddenMessage really works.

So yes, it is pretty clear shallow rendering is not helpful just as KCD argues.

But for him, this is proof that Guillermo Rauch was right all along: “integration testing” beats “unit testing.”

For me, the key insight I gleaned from looking at the Java analogue of HiddenMessage is not about the dichotomy between unit and integration testing, a controversy fraught with baggage because no one can even agree on how we define those terms to begin the conversation, but about why we test at all.

We write tests because we want to create something like a lab experiment—a controlled environment where we see how our code behaves when it is exercised just as clients will in production. It is a precondition that your code is initialized to some “ready state” so it can satisfy the public API contract it has with its clients. In Java, you typically accomplish that with a constructor (that may throw IllegalArgumentException if necessary) into which mock or stub dependencies are injected in test code to achieve the control we need—though some say that’s a smell in itself.

In the Java version of HiddenMessage, you have to work a little harder to mock the dependencies because they are private implementation details. Most Java developers would correctly find this awkward and smelly, but any HiddenMessage test is worthless if it isn’t supplied the tools it needs one way or another to satisfy its API contract. Shallow rendering fails because it eschews this basic concept—not because it proves integration tests beat unit tests.

So what now?

The whole point of mocks and stubs is to supply the code you’re testing with dependencies it needs to fulfill its contract in a way that’s deterministic so we have the control and predictability every experiment needs. Applying idioms from testing in Java can inspire you to consider any combination of these approaches for testing in React:

  • Use Jest as KCD recommends to mock components and side effects like REST calls just as a Java developer uses Mockito.
  • Use props to mimic constructor injection of mocked or stubbed dependencies. In fact, render props, which are now en vogue for sharing React components, essentially enable constructor injection of components. You can supply any component you want to a render prop in your test.
  • Use the functional nature of JavaScript to your advantage in testing. For example, REST calls are the most common side effects in JavaScript programming, and using TypeScript you could have a function prop fetch: (s: string) => Promise<string> to represent a REST call returning JSON. In your test, you supply a stubbed implementation like (s: string) => { Promise.resolve({id: 5}) } while your production code supplies a function with the same shape that makes real REST calls. This is analogous in Java to having a constructor parameter of type Function<String, CompletableFuture<String>> that you stub with (s) -> CompletableFuture.completedFuture({id: 5}) in your JUnit tests. In either case, no mocking library is needed.

Or…moving away from Java, stop being scared and embrace the Haskell-like idioms in Elm.

I prefer eschewing a third-party mocking library. For one thing, that’s one less dependency. More importantly, it makes your tests more durable as you avoid expectations about the implementation details of your component, which will change all the time. Focus on the intent of your code and its collaborators rather than the inner workings.

Don’t mistake a preference for a rule though. Despite the absolutist language in many programming blog posts out there, each approach has pros and cons. Nothing is all good or all bad. Keep all these options on the table—even the shallow rendering and snapshot testing that neither KCD nor I really likes, and choose the one that balances the time it takes to write a test with your level of confidence in the quality and durability of the outcome. You will likely find a blend of these approaches works best.

Conclusion

It’s great to see we have evolved from debating whether testing is necessary to debating how best to do it. As full stack engineers—equally adept at building efficient services as elegant user interfaces—become more common, it makes sense to leverage knowledge across domains. I’m no expert at testing in React, and I welcome the opportunity to integrate as much as I can from experts like KCD with my own experience testing on the server in languages like Java where we have tackled many of the same issues. I hope to see for myself which strategies work best to solve various problems in testing in React.

There will always be debate. If nothing else, let’s agree to focus on API contracts rather than implementation details and keep things simple so we don’t lose as many weekends refactoring tests every time a client wants a new feature.