Introducing Kompot

A utility library for testing React Native components using Detox

Nir Yosef
7 min readAug 2, 2018

In this post I will explain how and why I created Kompot, the trade-offs I made in the process, and the final architecture I ended up with.

A few words about Component testing

If you’ll search google for the term “Component testing”, you will find lots of vague and contradicting definitions. I am guessing it’s because no one directly invents and name all those different testing methodologies. They just sporadically evolve as the need calls, and after a while people try to classify them retrospectively. There are lots of grey areas, and it seems like no one can agree on a solid terminology. In the context of React, a React component is the building block of our app, and so, what I mean by component testing is the testing of React components independently from the rest of the app, as opposed to e2e tests. Keep in mind that a React component can be as small as a simple input field or as big as a full screen of your app.

The mother of mockings

Coming from a web development background, It was really hard for me to get used to the testing world of React Native. In the web, we can use jsdom to test our app with a frameworks like Enzyme. If, for example, you have a list of items, jsdom will allow you to render the list in memory, scroll down until the desired element is “visible”, and then click on it and expect some things to happen, all of this in a split second, headlessly. When I took my first steps in React Native’s world, I immediately tried to apply the testing methodologies that I know and love from the web, but I quickly found that assumption to be too optimistic. I could only use shallow rendering with enzyme, which in my experience is not strong enough. The problem is that you need to mock all the native stuff- View, Text, Button etc’. You need to mock everything. Lately, I came across this great post on medium that tried to tackle this problem and allow you to fully mount a component, but it is still immature, and I am not sure that mocking everything will be the right solution. Anyway, until a better mocking library emerges, I will stick to Detox for testing my components.

Using Detox

How do you test React components using Detox? Let’s say you have a simple <Dropdown/>, your options are:

  • E2E: Detox is an e2e testing library, so naturally, to test theDropdown using Detox in a proper way, you could just launch the app, navigate to the screen that contains the dropdown and test it. But E2E tests can be too fragile, slow, and could be pretty cumbersome to develop and maintain. For example, if your app contains a login page, you will need to successfully login before each test. E2E tests are good for testing a few critical flows of your app, but they are less suitable for testing a single component functionality.
  • Using example App: We can create an example app, that uses the dropdown with all of its modes, and then use Detox again to test it. The problem with example apps is that they are hard to maintain, and they are not scalable- we should be able to cover all the functionalities of the dropdown, so we need to create a huge page with dozens of dropdowns, or at least dozens of buttons that will each open a page with the dropdown in a certain mode. And we still need to use mock servers in order to mock things. If we have a more complex component instead of a simple dropdown, let’s say a component that fetches data from the server, we would still need to create a mock server and things get nasty really quickly.
  • Using Kompot: We’ll get to it in a second.

The Idea Behind Kompot

Kompot basically gives you the ability to mount your components on a real simulator, with given props and mocks, so that you could test each component using Detox. We’ll see an example of a spec file later, but for now let’s talk a bit about how it works.

First Iteration of the Kompot’s Architecture

Let’s say our app contains a complex component, sitting inside ComplexComp.js. The idea was creating a bundle, using ComplexComp.js as its entry point. Due to the nature of bundlers, we can be sure that only the packages that are important for running the component will be included in the bundle, thus keeping its size relatively small, even if our app codebase is big. It was important for making the tests run as fast as possible. The bundle will be served using a local server to a really small React Native app, that all it does is fetch the bundle and evaluate it on launch. So the Kompot framework was made out of three parts- A really small app, a local server, and a framework that is responsible for generating the bundles out of the components and coordinating the communication between the server and the app.

So, the POC with a simple project was successful, but as soon as I tried it on a real one, things turned bad.

The Kompot app had a different React Native version than what my project used, so it failed to load my components. On top of that, there were other difficulties during the integration with Detox. At that point, I decided to give up on the idea of a minimal separate app.

The Second Iteration And The Final Result

So, I decided to remove the Kompot app from the framework, and that means that the framework will use your real native app for launching the components, but it replaces your index.js file with a different one, thus none of your javascript code will run during the tests and your component will run in a relatively isolated environment. The benefit of using the real native app, is that when you add some custom native components to your app, you won’t need to mock them. This is a huge advantage that has absolutely passed by me on my first iteration, but lucky for us, shit happens, and reality made me do the right choices.

So the final result is much simpler- because we are now using the same app, we don’t need to bundle anything. We just tell the packager (the metro bundler) to use a different index.js file as its entry point, and the magic happens.

So How Does It Work?

Before we start, let me remind you that this is not a tutorial for how to use Kompot, it’s an explanation of how Kompot works, and it’s going to be a bit technical. If you are just interested in a tutorial you can skip to the docs.

The Kompot spec files

Let’s say again that you are testing the <Dropdown/> component. Start by creating a Kompot spec file (you should be familiar with the Detox syntax):

Kompot scans your projects for *.kompot.spec.js files, and searches for a kompotRequire statement. It then generates a js file that looks something like this:

Kompot is being shipped with an index.js that will be fed to the metro-bundler as the new entry point for the app during the tests. The index.js file requires this auto-generated file when it’s loaded, but before it does so, it sends a request to a local server and asks it: “which component are we testing right now?”. After the server responds with “Dropdown”, the index.js will set a global with the value “Dropdown”. Next, when the auto-generated file is being required, the statement if(global['Dropdown']) is evaluated to true and the dropdown is required and set as the current component to test.

But how does the server know to return “Dropdown” as the answer? Take a look again at the spec file, it contains these lines:

const component = Kompot.kompotRequire('./Dropdown');
....
await component.withProps({items: ['a','b','c']}).mount();

When we are mounting the component, we are actually sending a request to the local server, telling it we now want to test the Dropdown component, and then we are using a Detox command to refresh the app (inside the mount function): device.reloadReactNative(). So now when our index.js file is being reloaded, it asks the server again: ‘what is the current component that we are testing now?’, and the server will respond with ‘Dropdown’ and we are good to go!

No need for fake servers anymore

If you ever used Detox, you probably know that the only way to talk with your app is through fake servers or using a custom e2e.js extension to load different files under tests. But with Kompot, things are much easier. When Kompot processes your *.kompot.spec.js files and generates the auto-generated file from above, you can actually inject some code into this file, that will run before our component is being mounted:

KompotInjector will inject the code that has been given to it into the auto-generated file, and thus you can mock whatever you need in order to test the functionality of our component.

Final words

Kompot started as a POC, but it quickly became my choice for component testing, at least until a better headless solution pops up. If you find yourself using Detox to test your components/screens through sample apps, then Kompot is definitely a better alternative. If you are using Enzyme to test your components, then it’s up to you to decide. Kompot tests are just as easy to write as Enzyme tests, and although they are much slower to run, they will supply you with a much stronger tests. I already saw some failing tests because of1 the keyboard covering my input fields, and because of a ScrollView that was cutting at the bottom edge of the screen. Enzyme tests could never cover such cases.

But is it worth the decreased in speed and the increase in flakiness (tests on a real device will always be flakier)? The truth is that I just don’t know. Anyway, I personally don’t think that shallow rendering is strong enough, and I am not yet convinced that the current solutions for fully mounting components are mature enough, so at the meantime, my choice is Kompot.

Hope you enjoyed reading:)

--

--