SuperPumpup (dot com)
General awesomeness may be found here.

08 January 2016

React Native & Jest Testing

Objective:

Have a way of testing my RN components with Jest that Doesn't Suck.

Background:

There exists a reasonable corpus of knowledge around testing React components with Jest. React is relatively new, Jest is newer, and though each have documentation, their interop has some nuance. Then, React Native gets added to the mix with a whole 'nother set of nuances and things kind of get crazy. So I spent a few days trying to figure out something reasonable.

This example is of some components whose render functionality will be tested. The project uses Redux, so there is not much store interaction, and a lot of passing of big prop trees through components. It shouldn't matter too much to the testing, but knowing that may give more context.

High Level Strategy:

We're going to use ReactTestUtils' shallowRender function to generate a "one" (really two) layer virtual DOM tree to inspect, then do some assertions on that. The object we are inspecting never actually gets written to HTML, and we are not doing "real" HTML DOM test/inspect. Rather we are just inspecting the objects that React Native is going to paint to a screen (which, being RN, is not a web browser), and can make sure our logic is working based on that.

In order to do this we need a convenient way to "render" a component to something we can inspect, and we need some tools for making assertions about that result.

Execution

Composing

Setup

I'll start with the "header" of my test file.

'use strict';

jest.autoMockOff();

const shallowHelpers = require('react-shallow-renderer-helpers');
const findMatchingType = require('./findMatching').findMatchingType;
const objectAssign = require('object-assign');

import React, { View, Text } from 'react-native';

const NewLoan = require('../NewLoan');
const NewLoanForm = require('../NewLoanForm');
const PrepareSchedule = require('../PrepareSchedule');
  • jest.autoMockOff() - is important because if I don't have that, one of the components in my NewLoanForm object I include later will blow up. It's a bit of a liability, but not something that I've gone after fixing yet.
  • const shallowHelpers = require('react-shallow-renderer-helpers'); - this pulls in a bunch of React functionality for testing component trees (which is totally useless to us since RN components wind up being structured pretty differently from React components), but does pull in some nice functionality wrapping the shallowRender function.
  • var findMatchingType = require('./findMatching').findMatchingType; - this is a tool I built for doing assertions in the next section.
  • const objectAssign = require('object-assign'); this is useful for having our "default" props and being able to add new ones per-test. You could get similar functionality from lodash or similar, but I would prefer to keep that dependency out of my projects until necessary.

  • import React, { View, Text } from 'react-native'; - notice a couple of things here. First, this is the ES6 import syntax, where everything else uses require. This is because 1) I much prefer this syntax and 2) if I try using this syntax for the other lines, the autoMockOff(); won't work and these would be mocked - even if I ask for them not to be. This is due to JS hoisting, and may be fixed in the near future. The module that's being imported is a mock that will be detailed elsewhere.

  • const NewLoan, const NewLoanForm, const PrepareSchedule - these are not mocked and required in for doing assertions. I'm wanting to assert that when I render NewLoan with certain props, then it will have a NewLoanForm as a child, whereas if I give it different props, it will render a PrepareSchedule as a child. It may be nice to just do the comparison with string names, but this is good for now.

Render

It's nice extract a reusable render method for your components that you will be testing in different state, with different props, etc.

describe('NewLoan', () => {
  let newLoan;

  function renderNewLoan(props) {
    const defaultProps = {
      isFetching: false,
      loans: {
        preparedLoan: null,
        preparedSchedule: null,
        form: {
          fields: {},
          isFetching: false
        }
      }
    };
    const testProps = objectAssign(defaultProps, props)

    const shallowRenderer = shallowHelpers.createRenderer();
    shallowRenderer.render(() => <NewLoan {...testProps}/>);
    const output = shallowRenderer.getRenderOutput();

    return {
      props,
      output,
      shallowRenderer
    };
  }
  // Your assertions will go here
}

So I have a set of defaultProps that the NewLoan view will generally depend on to make its rendering decisions.

This defaultProps object then gets objectAssignd (merged, basically), with the provided props.

A renderer is constructed, the component rendered into it, and the output returned in an object.

Note that

return {
  props,
  output,
  shallowRenderer
};

Will really return an object like:

return {
  props: props,
  output: output,
  shallowRenderer: shallowRenderer
};

(thanks ES6 - and Obama).

Now let's render a component:

it('should display the NewLoanForm if there is no loan prepared', () => {
  const testProps = {};

  newLoan = renderNewLoan(testProps);
  const { output } = newLoan;
});

Sweet! We have something to look at. What is this output thing? console.log tells us:

Object {
  '$$typeof': Symbol(react.element),
  type: [Function: View],
  key: null,
  ref: null,
  props:
   Object {
     style: Object { flexDirection: 'column', flex: 1, width: 300, marginTop: 30 },
     children: [ [Object], [Object] ] },
  _owner: null,
  _store: Object {}
}

What I'm going to be asserting on generally is the type, props, and children - more likely the props of children when changing component state (though since this is Redux, MOST MOST MOST state should wind up being in the main state object and components only care about props & actions).

I was so excited about having this to assert on that my first tests when I got to this point were things like:

expect(output.props.children[0].props.children[0].type.displayName).toEqual('NewLoanForm');

I pasted that into our development Slack channel and immediately lost the respect of most of our engineering team. Well, at least that's my deepest fear. I'm sure they still love me. There were some (very correct) criticisms, and it was clearly time to move on to step 2. Making decent assertions.

Asserting

Gotchas

I'm not sure where this fits in the flow of this post, but I should mention something horrifying that I found. If you look at the assertion: expect(output.type.displayName).toEqual('NewLoanForm'); you may think, "Cool, it seems my React components have a property called DisplayName within their type that I can test against. Thanks React!" And you may even test against that for a little while and it will work. But then you will do that for a new component and get this result:

- Expected: undefined toEqual: 'NewLoan'

Wut?

It turns out that if you declare a component using the syntax: var NewLoan = React.createClass({}), JSX will helpfully add a property displayName to the component. If you do: export default class NewLoan extends Component {}, then no such luck. You have no displayName. I would like that day of my life to figure all that out back please.

Ok, Assertions For Real

There is a nice module for doing assertions against React components generated by shallowRender - https://github.com/sheepsteak/react-shallow-testutils. Sadly, almost none of the matchers (isComponentOfType, findAllWithClass, etc.) work in RN because a RN component is pretty different from a React component. Its findAll function does work pretty well, though (and it seemed kind of magical until I looked through it and realized it was just like an interview-question-type tree traversal).

Fortunately, this is all just JavaScript, and you can make your own matchers. These are what I came up with:

find[All]MatchingType

This will look for an element that matches what you're looking for. Simple. The All variant returns the array, the non-all variant just blows up if you don't have 1 exactly.

export function findAllMatchingType(tree, match) {
  return findAll(tree, (el) => {
    const typeMatch = match.type ? el.type === match.type : true;

    return typeMatch;
  }
  );
}

export function findMatchingType(tree, match) {
  const found = findAllMatchingType(tree, match);
  if (found.length !== 1) throw new Error('Did not find exactly one match');
  return found[0];
}

It should be noted that the type attribute that you are looking at here is sometimes friendly, sometimes a big nasty function. If you do the export default class... syntax, it has one form:

function PrepareSchedule() {
  _classCallCheck(this, PrepareSchedule);

  _get(Object.getPrototypeOf(PrepareSchedule.prototype), 'constructor', this).apply(this, arguments);
}

Otherwise, it can be:

function (props, context, updater) {
  // This constructor is overridden by mocks. The argument is used
  // by mocks to assert on what gets mounted.

  if (process.env.NODE_ENV !== 'production') {
    process.env.NODE_ENV !== 'production' ? warning(this instanceof Constructor, 'Something is calling a React component directly. Use a factory or ' + 'JSX instead. See: https://fb.me/react-legacyfactory') : undefined;
  }

  // Wire up auto-binding
  if (this.__reactAutoBindMap) {
    bindAutoBindMethods(this);
  }

  this.props = props;
  this.context = context;
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;

  this.state = null;

  // ReactClasses doesn't have constructors. Instead, they use the
  // getInitialState and componentWillMount methods for initialization.

  var initialState = this.getInitialState ? this.getInitialState() : null;
  if (process.env.NODE_ENV !== 'production') {
    // We allow auto-mocks to proceed as if they're returning null.
    if (typeof initialState === 'undefined' && this.getInitialState._isMockFunction) {
      // This is probably bad practice. Consider warning here and
      // deprecating this convenience.
      initialState = null;
    }
  }
  !(typeof initialState === 'object' && !Array.isArray(initialState)) ? process.env.NODE_ENV !== 'production' ? invariant(false, '%s.getInitialState(): must return an object or null', Constructor.displayName || 'ReactCompositeComponent') : invariant(false) : undefined;

  this.state = initialState;
}

There doesn't seem to be anything in there that makes it seem like it shuold be the right component, but if I compare it with a "dummy" component:

var FooClass = React.createClass({
  render() {
    return <View />
  }
})

It does not match. Who'da thunk.

find[All]Matching

This matcher will try to match both the type of the element and the props. It's a trivial extension of the previous matcher:

export function findAllMatching(tree, match) {
  return findAll(tree, (el) => {
    const typeMatch = match.type ? el.type === match.type : true;
    const propsMatch = objectMatches(match.props, el.props);

    return typeMatch && propsMatch;
  }
  );
}

export function findMatching(tree, match) {
  const found = findAllMatching(tree, match);
  if (found.length !== 1) throw new Error('Did not find exactly one match');
  return found[0];
}

I have extracted those modules into a file on my filesystem, and will probably extract it out to a react-native-shallow-testutils or similar as it gets more robust.

Putting it all together

This is a full test case:

it('should display the NewLoanForm if there is no loan prepared', () => {
  const testProps = {};

  newLoan = renderNewLoan(testProps);
  const { output } = newLoan;

  const match = findMatchingType(output, <NewLoanForm />);

  expect(match).toBeTruthy();
});

There we go. The contents of match in this case are the NewLoanForm component instance that got rendered, but mostly all I'm checking at THIS level of testing is whether by "default" it will render a form. In other tests I vary the props to have it render other things (more loan info collection, confirmation view, submission successful view, etc)

Conclusion

This has been hard. I'm pretty good at the internet, and still, it's been very hard. I'm very grateful to Facebook for getting this great tech out there, and really look forward to watching these projects evolve.

Resources:

  • http://www.asbjornenge.com/wwc/testing_react_components.html
  • http://www.schibsted.pl/2015/10/testing-react-native-components-with-jest/ • I did not like that this uses the shallowRender technique, and tried to avoid it. However, @cpojer suggested that that is a good way to test components
  • https://jamesfriend.com.au/better-assertions-shallow-rendered-react-components
  • Reactiflux Discord - #flux


14 December 2014

Internalizing Dependencies

So I saw an interesting conversation today on Twitter between @DHH and @thijs this morning. They were discussing the NewRelic IPO and the subject got around to running ancient Rails versions, and the fact that a lot of the migration projects from Rails 2.3 have been driven in large part because of the movement of the surrounding ecosystem (read, gem updates).

Continue reading


08 December 2014

Packaging Up A Vert.X App

This is a work in progress... After @sigil66's tech talk at Boundary, I've been inspired to try to understand packaging and how to use packaging to deploy my software.

This is an application written in JRuby running on the Vert.X platform - so this will be an exploration around deploying a Vert.X application (which there is not a ton of documentation on, in any case...)

Continue reading


27 August 2014

Bitcoin is Like Computing in the 90's

I've done some mining of Dogecoin and a few altcoins, and owned a tiny bit of bitcoin, but never really much. I'd run the wallet applications for the altcoins I'd used in the past and it was annoying but fine. Most of my bitcoin transactions have been on hosted wallets or signed using a command line client.

I've often seen people complaining about using the bitcoin wallets on their computers for a number of reasons, but hadn't experienced the pain.

I recently met with Trace Mayer, and he was raving about his Armory product. I recently downloaded it to see how it would do. First impressions were "meh". I imported a few vanity addresses, backed them up to some encrypted USB drives, etc, and then thought I'd start working with them.

Then I see that Armory doesn't run the node software itself, it will find a locally running Bitcoin-QT instance. "Ugh", I thought, "I'll finally get Bitcoin-QT up and running. How bad can it be?"

It's awful

Yesterday morning I found something describing how I can use BitTorrent to download a copy of the 20 gigabyte blockchain. So, ok, did that. That took me about that whole day. And now my laptop has 20 gigs less space available. That's 20% fewer baby pictures my laptop can have so that I can, "Have my own money on my computer." Given that I've paid a lot more money than I own in bitcoin to recover baby pictures off failed hard drives, this is not a winning proposition at the outset.

Then, 20 GB file downloaded and placed, I open the client - expecting it to import the wallet in a few seconds. I mean, shit, I'd just spent 9 hours downloading a 20gb file.

NOPE

9 hours of terrifyingly-hot-laptop-operation later and the client has ALMOST finished importing that file.

What the heck, people

This is a huge opportunity. Digital currencies can change a lot of things about the world. You've had 5 years and over a year of a billion dollar market cap - that means lots of people have tens (maybe hundreds) of millions of dollars worth of bitcoin at their disposal and a really quite liquid market. And this is the garbage you want people to use?

You think that your bitcoins will become worth a million dollars each with the software being this steaming pile of shit?

I guess it's just more of an announcement that there is a TON of work to be done. I just wish there were people with the foresight to fund it with some newly-minted wealth...


12 August 2014

Mode Analytics Is Helping Me Become A Better Developer

Like most other "software writers" who primarily write Rails code I've met with, I don't have any formal CS training - or any formal training at all, really. I just started learning how to build things, which was hard and horrible in PHP and spectacularly easy in Rails. Love it or hate it, it's SO EASY to build systems in Rails when you have no idea what you're doing. I'll always argue that, and think it's amazing.

That said, I never learned SQL well...

Continue reading


12 August 2014

ReactJS and Vert.x

I'm building a new project in Vert.x and it's amazing to be able to separate concerns well. However, I "miss" having a Rails project to fall back on. Here I'll talk a bit about Vert.x and exploring using the ReactJS framework to build the UI.

Continue reading


22 July 2014

Docker & RackConnect

To enable Docker containers to connect to the external network in a RackConnected environment, touch /etc/rackconnect-allow-custom-iptables. Otherwise RackConnect will destroy all Docker's forwarding rules.

Longer Version

So I just got bitten by a little issue using Docker & RackConnect. I had a nice fresh server brought up with lots of containers running and everything was grand. Ish.

I found two different problems, both with the same solution. On the one hand, I would start a service container (RabbitMQ), and then link to it with a "web" container, and then go to my RackConnect config and open a port to test connecting to the "web" container (that would have a port number like 49156 or whatever), but then all of a sudden that container wouldn't be able to access the service container.

Also, from within any container, I couldn't access the network.

curl google.com or ping 8.8.8.8 were just timing out.

I couldn't find a direct answer to this question, but it turns out that docker writes a ton of iptables FOWARD entries during the course of doing its thing, and those periodically get clobbered by RackConnect when it runs.

This article tells how to let RackConnect know to leave your custom rules alone - it does a merge rather than a force-overwrite. So touch that file, restart Docker, and be on your way!