UI library agnostic?

One of the most desirable qualities of any codebase is low coupling since this enables easy and orderly ways to change it. Easiness of change is what enables product teams to add or remove features at a fast pace which in turn makes the product more agile.

The user interface is one of the parts with the most frequent changes therefore, its code must be as easy to change as possible. I have worked with a handful of UI libraries and noticed that usually the front-end code is very coupled to whatever lib being used.

What if you could have the front-end code so decoupled that changing the UI library would not be a total rewrite?

Imagine that one day your customers are completely fed-up that your product is super slow and that the culprit is the front-end which is completely bloated. Your team decides to do a total rewrite with a different UI library that is focused on being lightweight. It would be a big win if any working code could be salvaged and reused.

I decided to do a little experiment to try answering my question. My goal was to create a front-end only Todo App with some constraints:

  • Minimal and simple: no need to add many features. The goal is to show how decoupled the core can be from the UI library.
  • The core functionalities must be implemented with vanilla JavaScript and have no external dependencies.
  • The core TodoApp must be exactly the same regardless of UI library.
  • Implement the UI with vanilla JavaScript.
  • Implement the UI with React.
  • Implement the UI with Svelte.
  • Use Tailwind CSS for the styles.

As you can see from the TodoApp tests, it’s pretty basic:

  • The todo item data is: { id: number, title: string, done: boolean }.
  • Add, remove, and edit todo items by id.
  • Get all items.
  • Filter the todo items by done or not done.
  • Throws errors when:
    • creating an item without a title.
    • deleting an item that is not done.
    • trying to perform an operation in an inexistent item.

The UI is also simple:

  • A required text input field for typing the todo.
  • A submit button to add the item. Enabled only when the input has some value. This prevents errors from trying to add an item without a title.
  • A radio group with 3 options for selecting the active filter: all, active (not done), or done.
  • A list for showing the items based on the selected filter.
  • Each item displays its title. If the item is done, add a line through it and add a delete button next to it. This prevents errors from trying to delete items that are not done.
  • Each item has a checkbox for marking it done or not.
  • A footer with the name of the UI library being used.

Unfortunately, I didn’t have the will to write tests for any of the UI implementations. Ideally, I would like to have just one implementation of automated tests and it would work independently of UI library as well. Maybe in another blog post I can explore this.

I decided to start with the vanilla JS implementation. An important question arose early on code: How’s going to be the dependency between the UI and the app code?

Designing a plug for the UI

The first approach was pretty straight forward: the UI imports the app. When the UI is initialized, it creates an instance of the TodoApp and can easily call all its functions and access all the data.

import TodoApp from "./app";

export default function VanillaUI() {
  const app = new TodoApp();
  // ...
  return {
    init: () => {...}
  };
}

This approach had a few bad code smells:

  • The app “lives” inside the UI completely encapsulated which is a super high coupling.
  • Hard to test since it’s not possible to mock any data or functionality in an elegant way.
  • Any API changes in the TodoApp breaks the UI.

In the second approach, I used dependency injection: instead of the UI importing the TodoApp, an instance is given when the UI is initialized. This solved the first two issues from the first approach. Some code is now necessary to integrate both: it initializes the app and the UI and passes the reference of the former to the latter.

export default function VanillaUI() {
  let app;
  // ...
  return {
    init: (todoApp) => {
      app = todoApp;
      // ...
    }
  };
}
// Integration code
import TodoApp from "./app";
import VanillaUI from "./vanilla.ui.2";

const app = new TodoApp();

VanillaUI().init(app);

In the third approach, to solve the last remaining code smell, I used inversion of control: the UI provides an interface for the functionalities it depends on to operate. Since the UI code depends on something it controls, it’s completely safe from any external changes.

If you’re into types, here’s how the overall idea would look like in TypeScript:

interface TodoItem {
    id: number;
    title: string;
    done: boolean;
}

interface UIDependencies {
    getAll: () => Promise<TodoItem[]>;
    getDone: () => Promise<TodoItem[]>;
    getNotDone: () => Promise<TodoItem[]>;
    onAddItem: (item: TodoItem) => Promise<number>;
    onTodoChange: (item: TodoItem) => Promise<number>;
    onDeleteItem: (todoId: number) => Promise<number>;
}

function VanillaUI(adapter: UIDependencies) {...}

As a good measure, the UI dependencies is asynchronous. The UI updates/re-renders only when the TodoApp is done with its work and resolves the promise.

The integration code has a bit more work to do now: it must implement the UIDependencies interface and call the right TodoApp functions when needed.

export default function VanillaUI(uiDeps) {
  // ...
  return {
    init: () => {...}
  };
}

// Integration code
import TodoApp from "./app";
import VanillaUI from "./vanilla.ui.3";

const app = new TodoApp();

const uiDeps = {
  getAll: async () => app.todos(),
  getDone: async () => app.filters.done(),
  getNotDone: async () => app.filters.notdone(),
  onAddItem: async item => app.add(item),
  onTodoChange: async ({ id, done }) => {
    app.edit(id, { done });
    return app.todos().find(todo => id === todo.id);
  },
  onDeleteItem: async id => {
    app.delete(id);
    return id;
  }
};

VanillaUI(uiDeps).init();

Plugging in different UIs

Once I was happy enough with the results of the Vanilla JS implementation, I started with the React implementation. I followed the 3rd approach of the Vanilla JS implementation as the basis.

The React implementation is straight forward even though it’s a bit verbose. I even tried a “god component” with multiple useState(). It reduced the amount of code by a good amount but it’s still unnecessarily hard to read. I guess React is just too verbose in nature. ????

// ... code redacted for brevity ...
export default function ReactUI({
  uiDeps
}) {
  const [{ todos, inputValue, activeFilter }, dispatch] = useReducer(
    reducer,
    initialState
  );

  const fetchTodos = async filter => {
    let getTodos = getAll;
    if (filter === Filters.ACTIVE) {
      getTodos = getNotDone;
    } else if (filter === Filters.DONE) {
      getTodos = getDone;
    }

    const todos = await getTodos();
    dispatch({ type: SET_TODOS, todos });
  };

  useEffect(() => {
    fetchTodos(activeFilter);
  }, [activeFilter]);

  const handleSubmit = event => {
    event.preventDefault();

    onAddItem({ title: inputValue }).then(() => {
      fetchTodos(activeFilter);
    });

    dispatch(clearInput);
  };
}

// Integration code
import React from "react";
import ReactDOM from "react-dom";
import TodoApp from "./app";
import ReactUI from "./react.ui";

const app = new TodoApp();

const uiDeps = {
  // Identical to vanilla JS ...
};

ReactDOM.render(
  <React.StrictMode>
    <ReactUI uiDeps={uiDeps} />
  </React.StrictMode>,
  document.getElementById("root")
);

The Svelte implementation was a breeze of fresh air! It was by far the easiest UI to implement and I have zero experience with it. I ended up doing a “god component” but it was not intentional. I really just don’t know yet how to create internal private components in Svelte. ????

<script>
  export let uiDeps = null;

  let inputValue = "";
  let selectedFilter = "all";
  let todos = [];

  function onFilterClick(filter) {
    selectedFilter = filter;
    fetchTodos();
  }

  async function fetchTodos() {
    let getTodos = uiDeps.getAll;
    if (selectedFilter === "notdone") {
      getTodos = uiDeps.getNotDone;
    } else if (selectedFilter === "done") {
      getTodos = uiDeps.getDone;
    }

    todos = await getTodos();
  }
  // code redacted for brevity ...
  fetchTodos();
</script>

// Integration code
import SvelteUI from "./svelte.ui.svelte";
import TodoApp from "./app";

const app = new TodoApp();

const uiDeps = {
  // identical to vanilla JS ...
};

// TODO add <html> and <body> Tailwind classes
const ui = new SvelteUI({
  target: document.body,
  props: { uiDeps }
});

export default ui;

Conclusion

That was a really fun experiment! It’s definitely possible to make your front-end code agnostic of the UI library being used. The integration between the two becomes very explicit and can be more or less decoupled.

Now, is it practical? Is it worth it? To be honest, only real-life experience will tell. The UI code in all three libraries was much bigger than the core application. Maybe we could have put some more logic in the core app that would simplify the UI but I don’t think it would make a big difference.

The deal-breaker lies in the UI code that can become more complex, even awkward since part of the state – the business part – is managed externally. The Return on Investment of this approach would only come later when the UI library indeed has to change.

That’s it! Let me know if the code could be improved especially in Svelte that I’m a total noob.

Cheers!

Test safely in the shallows

Hello once again! Today I’ll share some more insights regarding Enzyme’s shallow rendering. It’s a great tool for isolating and controlling the rendering of components in unit tests. Imagine an <App> component that uses many other children components that also have many other children:

import { Router, Route } from "router"
import { useFeed } from "./hooks"
import { Navbar, Spinner, FeedHighlights, HomePage, ProfilePage, Footer } from "./components"

const App = ({userData}) => (
  const [isLoadingFeed, feed] = useFeed(userData.id)
  <Navbar />
  { isLoadingFeed ? <Spinner small /> : <FeedHighlights feed={feed} /> }
  <Router>
    <Route path="/">
      { isLoadingFeed ? <Spinner /> : <HomePage feed={feed} /> }
    </Route>
    <Route path="/me">
      <ProfilePage user={userData} />
    </Route>
  </Router>
  <Footer />
)

export default App

How can you isolate and test only the component’s functionality? If you use mount(<App userData={mockUserData} />), the whole component tree is rendered, including the whole component’s lifecycle, so a lot of code not part of the component under test will run. There’s a good chance that a bug or a change in one of these dependencies would also break the unit tests of <App>. The component itself does not do much but it integrates many other parts. Good luck trying to mock everything that lies beneath.

The usual solution for this problem is to apply Inversion of Control and Dependency Injection. Here’s how we could re-write <App> using IoC and DI principles: we stop importing all external dependencies and specify them all as props.

const App = ({userData, useFeed, Navbar, Spinner, FeedHighlights, HomePage, ProfilePage, Footer, Router, Route}) => (
  const [isLoadingFeed, feed] = useFeed(userData.id)
  <Navbar />
  { isLoadingFeed ? <Spinner small /> : <FeedHighlights feed={feed} /> }
  <Router>
    <Route path="/">
      { isLoadingFeed ? <Spinner /> : <HomePage feed={feed} /> }
    </Route>
    <Route path="/me">
      <ProfilePage user={userData} />
    </Route>
  </Router>
  <Footer />
)

export default App

So in <App> tests we would need to do something like this:

const MockComponent = {children, ...props} => <div {...props}>{children}</div>
const mockUseFeed = useMockHook()

const wrapper = mount(
  <App
    userData={mockUserData}
    useFeed={mockUseFeed}
    Navbar={MockComponent}
    Spinner={MockComponent}
    FeedHighlights={MockComponent}
    HomePage={MockComponent}
    ProfilePage={MockComponent}
    Footer={MockComponent}
    Router={MockComponent}
    Route={MockComponent}
  />
);
// asserts and expectations ...

As you can see it’s already horrible and now consider adding the proper interface and primitive type for each prop! It’s doable, mount won’t cause harm since all dependencies are mocked but it’s a completely nuts approach. Not worth demonstrating it. Since IoC and DI do not work really well in these scenarios, shallow(<App userData={mockUserData} />) comes in to save the day! We can keep the initial implementation and still test App‘s functionality in isolation. Have your cake and eat it!

The danger lurking in the shallows

Here comes the biggest limitation of shallow: your tests can become really attached to implementation details and fragile to harmless refactors. See this implementation of a <ListGroupItem> functional component that has no external dependencies to render HTML elements:

import isFunction from "is-function"

const ListGroupItem = ({children, onClick}) => {
  if(isFunction(onClick)) {
    return (
      <button onClick={onClick} className="list-button">{children}</button>
    )
  } else {
    return (
      <li className="list-item"><span>{children}</span></li>
    )
  }
)

export default ListGroupItem

It’s tests could be something like this:

let wrapper = shallow(<ListGroupItem>item</ListGroupItem>);
expect(wrapper).toContainExactlyOneMatchingElement("li.list-item");
expect(wrapper).toHaveText("item");

wrapper = shallow(<ListGroupItem onClick={() => {}>clickable item</ListGroupItem>);
expect(wrapper).toContainExactlyOneMatchingElement("button.list-button");
expect(wrapper).toHaveText("clickable item");

The tests are using shallow and they all pass so you go and refactor the component’s code so that it’s a bit easier to read:

import isFunction from "is-function"

const ListButton = ({children, onClick}) => <button onClick={onClick} className="list-button">{children}</button>

const ListItem = ({children}) => <li className="list-item"><span>{children}</span></li>

const ListGroupItem = (props) => (
  {
    isFunction(props.onClick)
    ? <ListButton {...props} />
    : <ListItem {...props} />
  }
)

export default ListGroupItem

Now your tests fail and you curse because it shouldn’t have since you only changed the component’s internals. shallow is being way too restrictive now. In this case, the right tool for the job is actually render or mount since the whole component tree is internal to the subject under test. Cool, we refactor the tests to use mount so the tests pass again.

Then you decide to use the existing external Button component since it fits perfectly for this case:

import isFunction from "is-function"
import Button from "./Button"

const ListItem = ({children}) => <li className="list-item"><span>{children}</span></li>

const ListGroupItem = (props) => (
  {
    isFunction(props.onClick)
    ? <Button className="list-button" onClick={props.onClick}>{children}</Button>
    : <ListItem {...props} />
  }
)

export default ListGroupItem

Now what!? To test the code in isolation you use shallow and then verify that Button was used with the correct props but in order to verify that the <li> was rendered correctly, you have to dive in the rendering:

expect(wrapper.find("ListItem").dive()).toContainExactlyOneMatchingElement("li.list-item");

Ok, it works but it doesn’t scale well because if the component structure changes, not its rendered elements, the tests would have to dive accordingly. There’s a small sneaky trick that we can do that would solve the diving issue and still use shallow: use the internal component in an imperative manner.

const ListGroupItem = (props) => (
  {
    isFunction(props.onClick)
    ? <Button className="list-button" onClick={props.onClick}>{children}</Button>
    : ListItem(props)
  }
)

That’s the beauty of functional components: they are just functions! You can skip declarative programming with JSX and it just works. Now you don’t need any dive calls in the tests since there are effectively no more extra components to be rendered. Have another cake and eat it too!

Conclusion

Enzyme’s like a power tool for testing React applications. If used correctly, it can make you very effective and productive. If used carelessly, you can lose a finger.

  • Use shallow to limit the rendering of components with many external dependencies so you can test the propper code in isolation.
  • Inversion of Control and Dependency Injection principles don’t fit very well in React.
  • Use render or mount to test components that don’t depend on other external components.
  • Avoid render dive because it ties the tests to the internal implementation of the component.
  • Be sneaky when you can: declarative JSX is cool but optional. Use components in imperative fashion to save some headaches.

I hope this helps. Cheers!

Confident React App – Part 5

Here we go again! The “Confident React App” series of posts is finally back after a long period away. You can follow from the start.

From the feature specs – defined in the 1st post of the series – we can observe some important information regarding interactions:

  1. list item selection is toggled when clicked.
  2. the list group works with a single selection of list items.

The second point was not explicit from the specs (and it was intentional so that you could comment on it. Did you notice it?) but we discovered it after some (imaginary) team discussion (backlog grooming, pre-iteration meeting, etc). The team must extensively communicate verbally and written in preparation for the implementation. Many questions arise when the code gets written, especially in the early stages of development. Effective communication is key!

With that in the clear, it’s time to start coding the user interaction of our app.

Oh, behave…

We’re introducing a new component which whole’s responsibility is to handle user interactions and state management: <SingleSelectionListGroup>.

We’ve “transformed” the component’s specification into tests:

// src/components/listGroup.spec.js
// @flow
describe("<SingleSelectionListGroup />", () => {
  it('renders according to specification', () => {});

  describe('when an item is clicked', () => {
    describe('and it is NOT selected', () => {
      it('selects the clicked item', () => {});
      it('deselects any other selected item - single selection', () => {});
    });
    
    describe('and it IS selected', () => {
      it('deselects the clicked item', () => {});
    });

    it('calls the given onChange callback with the current selected index and value', () => {});
  });
});

Now it’s the tricky part: implementing the tests in a way that it’s not coupled with the component’s implementation. 

This is important because we want to be able to refactor our code in ways that better suit the implementation needs without having to modify any of its tests. Attention: if the public API changes, that’s not a refactor! Code refactoring is the process of restructuring existing computer code without changing its external behavior.

You may ask yourself, “What is knowing implementation details?”. If the test code looks for an internal id or CSS class, is it an implementation detail? How about looking for a dependent component? Maybe a function from a 3rd party library? If the test knows one private implementation detail, does it mean that it can use any other private implementation detail as well?

To be honest, I don’t have a definitive answer to this dilemma. There are as many answers as there are development teams out there. And there’s no correct answer, only trade-offs. Mine, which gives me confidence in my code, is as follows.

We want to test the functionality of <SingleSelectionListGroup> component and we know that it must render according to Bootstrap’s style, therefore, it will use <ListGroup> and <ListGroupItem>. But the rendering part is just a detail for the new component. It doesn’t care about HTML tags or CSS classes. We just need to test that the render components are used correctly to represent the state and behavior.

Let’s do a quick thought exercise. Imagine that we want to know as little as possible about the rendering details. One way to achieve this is to incorporate the “end-user point of view”. This means writing tests that rely only on the artifacts that end up in the user’s browser/client like text labels and markup. We could test a bunch of features with these artifacts since most of them are possible to know or mock in the test.

There’s a particular caveat in the list item selection case. In order to test it, it is required to look for the active CSS class. But this class is a rendering detail that is the responsibility of the <ListGroupItem> component. Putting it in another way, the tests would know the details of the details.

Some teams argue that “the tests don’t care how active CSS class got rendered as long as it’s there.” And that works too. The main disadvantage is that if <ListGroupItem> changes class name from active to selected, <SingleSelectionListGroupItem> tests would fail for no reason. It’s a change totally unrelated to the component being tested so the tests should not break.

To me, the approach that gives the most confidence is testing that <SingleSelectionListGroup> uses <ListGroup> and <ListGroupItem> correctly, passing the right props. Yes, the test will know some implementation details but it won’t know the details of its internal components. This approach also protects <SingleSelectionListGroup>‘s tests from any changes internal to <ListGroup>and <ListGroupItem>.

More on this topic later. Here’s a test implementation that follows our guidelines. Red:

// src/components/listGroup.spec.js
describe("<SingleSelectionListGroup />", () => {
  it("renders according to specification", () => {
    const wrapper = shallow(
      <SingleSelectionListGroup>
        <ListGroupItem>Item 1</ListGroupItem>
        <ListGroupItem>Item 2</ListGroupItem>
      </SingleSelectionListGroup>
    );

    const listGroup = wrapper.find(ListGroup);
    expect(listGroup).toExist();
    expect(listGroup.find(ListGroupItem)).toHaveLength(2)
  });

  describe("and when an item is clicked", () => {
    describe("and it is NOT selected", () => {
      it("selects the clicked item", () => {
        const wrapper = shallow(
          <SingleSelectionListGroup>
            <ListGroupItem>Item 1</ListGroupItem>
            <ListGroupItem>Item 2</ListGroupItem>
          </SingleSelectionListGroup>
        );

        wrapper.find(ListGroupItem).first().simulate("click");

        expect(
          wrapper.find(ListGroupItem).first()
        ).toHaveProp("active", true);
      });

      it("deselects any other selected item - single selection", () => {
        const wrapper = shallow(
          <SingleSelectionListGroup>
            <ListGroupItem active>Item 1</ListGroupItem>
            <ListGroupItem>Item 2</ListGroupItem>
          </SingleSelectionListGroup>
        );

        expect(
          wrapper.find(ListGroupItem).first()
        ).toHaveProp("active", true);

        wrapper.find(ListGroupItem).last().simulate("click");

        expect(
          wrapper.find(ListGroupItem).first()
        ).toHaveProp("active", false);

        expect(
          wrapper.find(ListGroupItem).last()
        ).toHaveProp("active", true);
      });
    });

    describe("and it IS selected", () => {
      it("deselects the clicked item", () => {
        const wrapper = shallow(
          <SingleSelectionListGroup>
            <ListGroupItem>Item 1</ListGroupItem>
            <ListGroupItem active>Item 2</ListGroupItem>
          </SingleSelectionListGroup>
        );

        expect(
          wrapper.find(ListGroupItem).last()
        ).toHaveProp("active", true);

        wrapper.find(ListGroupItem).last().simulate("click");

        expect(
          wrapper.find(ListGroupItem).last()
        ).toHaveProp("active", false);

        expect(
          wrapper.find(ListGroupItem).first()
        ).toHaveProp("active", false);
      });
    });

    it("calls the given onChange callback with the current selected index and value", () => {
      const changeSpy = jest.fn();
      const wrapper = shallow(
        <SingleSelectionListGroup onChange={changeSpy}>
          <ListGroupItem>Item 1</ListGroupItem>
          <ListGroupItem>Item 2</ListGroupItem>
        </SingleSelectionListGroup>
      );

      wrapper.find(ListGroupItem).first().simulate("click");

      expect(changeSpy).toHaveBeenCalledWith({
        index: 0,
        value: "Item 1",
      });

      wrapper.find(ListGroupItem).first().simulate("click");

      expect(changeSpy).toHaveBeenCalledWith({
        index: -1,
        value: null,
      });
    });
  });
});

Notice how the test above never looks for HTML specific details like tags or CSS classes. Those details are for the render components. The new tests only care about what the component really is supposed to do: handle the selection, user interactions, and use the propper render components correctly.

Also notice that the tests only used shallow rendering! That’s the utility tool for isolating the component rendering. This forces the implementation to be very flat which is fine for the current needs. Granted, if the component structure was more complex we would need to use Enzyme’s dive() as well in the tests which are also implementation details.

Now go ahead and make all tests turn green. It’s a pretty cool challenge. You can check my solution on GitHub. Don’t forget to add a new Storybook story with the new component to see it in action.

This is it for today’s entry! I hope the confidence in your code has increased a little bit more with my explanations.

Until next time when, hopefully, this series will end.
Cheers!

Confident React App – Part 4

Welcome to the continuation of the Confident React App blog post series. We’ve covered some considerable ground so take your time reading the series (part 1 | part 2 | part 3).

In this post, we’ll add a tool to our arsenal for testing components in the browser without much hassle: Storybook.

Telling component stories

We already used some important tools and techniques to our code base to increase our confidence:

  • Automated tests
  • Static type checking
  • Behavior Driven Development

But can you spot what’s missing so far? Well, it’s the application running in a browser, just like the end-user will. A dummy app for testing the components would be fairly easy to create but there a few downsides for this approach:

  • The real app depends on external APIs/resources so it’s slower.
  • It could be impossible to test some features due to the unavailability of the external APIs/resources.
  • The app code is more complex since it integrates many parts which makes it hard to verify components in isolation.
  • Apps can have many complex states which makes it hard to verify all use cases, especially error states.
  • It’s harder to iterate fast in the UI when there are too many dependencies.

A common way to avoid the pitfalls above is to create a separate app for the purpose of showcasing components and app states in a more structured and isolated way. Enter Storybook: the tool of choice for the question at hand. Storybook is quite powerful and versatile so I recommend reading through its examples and documentation. Let’s add it to our project:

npx -p @storybook/cli sb init

yarn flow-typed install @storybook/[email protected] @storybook/[email protected]

If everything goes well after the long install, there will be a bunch of new files in our project. Storybook is also already running with some demo content.

Storybook uses an original language for its API: you are supposed to showcase the features of your application in stories by module. For example, we want to showcase the <ListGroup> component so a story is added for it with “chapters” that show all it can do.

Edit the file src/stories/index.js and add the following code:

// @flow
import React from 'react';
import { storiesOf } from '@storybook/react';
import { ListGroup, ListGroupItem } from '../components/listGroup';

storiesOf('ListGroup', module)
.add('with items', () => (
  <ListGroup>
    <ListGroupItem>item 1</ListGroupItem>
    <ListGroupItem>item 2</ListGroupItem>
  </ListGroup>
));

Now, if you go to http://localhost:9009/?path=/story/listgroup–with-items, you can actually see and try for the first time the component!

… And it sucks. Even though the generated HTML is perfectly valid, it doesn’t look at all to the Bootstrap version:

This is exactly why we always need to test the application, or parts of it, in the browser; even with all the tools and techniques added so far, we’re still very far from the desired outcome.

You gotta have style

We’ve been talking a lot about Bootstrap and even implemented some code that relies on it, but we never added it as a dependency to our project.

There are many ways of fulfilling this step. For simplicity, we’re adding Bootstrap as a runtime dependency so only its production CSS is loaded by the page. To do so, we have to add custom tags to Storybook. Create the file .storybook/preview-head.html and add this:

<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">

Bootstrap’s CSS will be loaded from a CDN inside of Storybook. Just restart Storybook and profit!

Enriching the leaves

OK, we have a very simple list of items but there are still two important functionalities missing:

  1. Active list items.
  2. Clickable items so that they are interactive.

Let’s jump straight into the red test:

it('renders active when active prop is truthy', () => {
  const wrapper = shallow(<ListGroupItem active>item 1</ListGroupItem>);

  expect(wrapper)
    .toContainExactlyOneMatchingElement('li.list-group-item.active');
});

And implement it to get a green test:

type ListGroupItemProps = {
  children: string,
  active?: boolean
};

export function ListGroupItem (props: ListGroupItemProps) {
  const classes = "list-group-item" + (props.active ? " active" : "")
  return <li className={classes}>{props.children}</li>;
}

Finally, a new story needs to be added so we can see the final result:

.add('with active items', () => (
  <ListGroup>
    <ListGroupItem active>item 1</ListGroupItem>
    <ListGroupItem active>item 2</ListGroupItem>
  </ListGroup>
));

For the clicking, the specification defines two options: either an <a> or <button>. Since we don’t need page navigation yet, the <button> is the right choice. Also, whenever an item is clicked, it must execute the given callback if any. The red tests:

describe('when onClick prop is defined', () => {
  it('renders as an action button', () => {
    const wrapper = shallow(
      <ListGroupItem onClick={() => {}}>
        item 1
      </ListGroupItem>
    );

    expect(wrapper).toContainExactlyOneMatchingElement(
     'button[type="button"].list-group-item.list-group-item-action'
    );
  });
  
  it('renders as an active action button when active prop is truthy', () => {
    const wrapper = shallow(
      <ListGroupItem active onClick={() => {}}>
        item 1
      </ListGroupItem>
    );
 
    expect(wrapper).toContainExactlyOneMatchingElement(
      'button[type="button"].list-group-item.list-group-item-action.active'
    );
  });

  it('calls the given callback when clicked', () => {
    const onClickSpy = jest.fn();
    const wrapper = shallow(
      <ListGroupItem onClick={onClickSpy}>item 1</ListGroupItem>
    );

    wrapper.simulate("click");

    expect(onClickSpy).toHaveBeenCalled();
  });
});

To make the tests pass/green:

type ListGroupItemProps = {
  children: string,
  active?: boolean,
  onClick?: Function
};

export function ListGroupItem (props: ListGroupItemProps) {
  let classes = "list-group-item" + (props.active ? " active" : "");

  if(props.onClick) {
    classes += " list-group-item-action";
    return (
      <button type="button" className={classes} onClick={props.onClick}>  
        {props.children}
      </button>
    );
  } else {
    return <li className={classes}>{props.children}</li>;
  }
}

The last step is to verify it in the browser with a story:

import { action } from '@storybook/addon-actions';

.add('with actionable items', () => (
  <ListGroup>
    <ListGroupItem active onClick={action("Item 1 clicked!")}>
      action item 1
    </ListGroupItem>
    <ListGroupItem onClick={action("Item 2 clicked!")}>
      action item 2
    </ListGroupItem>
  </ListGroup>
));

There you go: our React implementation of the Bootstrap’s list group component is finally complete!

But what do we do with it? The answer lies in the app’s requirements: selecting an item when it’s clicked, multiple or single selections, deselecting…

In the next post, we’ll implement the behavior with some state and do a deeper discussion on how to test components in a way that makes us confident.

Cheers!

Confident React App – Part 3

This is the third post on the series about Confident React App where I show how I build up confidence in the source code of my React apps. Here’s the beginning of the story.

In this post, we’ll finally start coding some React components but following some vital rules that will help us make reliable code.

Coding confidently with BDD + TDD

Research has shown that Test Driven Development is one of the most effective measures for increasing the perceived quality of the developed software: from ~40% to ~80% increase. They also measured the increase in effort/cost for applying it.

We can’t skip such an effective technique, right? But how do we do it? Well, there’s the TDD bible to study and also
“The Three Laws of TDD” to follow:

  1. You are not allowed to write any production code unless it is to make a failing unit test pass.
  2. You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.
  3. You are not allowed to write any more production code than is sufficient to pass the one failing unit test.

That’s much easier said than done. After months of practice, it still takes me a while to get into the TDD flow so I need to be very aware to not break the rules. Because of the accrued learning curve and effort, it’s easy to make mistakes and misuse it which causes some resentment. The most common issue I observe is test code that is too coupled with implementation details, especially in React apps.

I suspect that this happens because of the mindset the developer is in when doing tests first: you get framed on testing everything! However, 100% test coverage is counterproductive since not every part of your code is equally worth the test hassle and it’s also an illusion.

The Behavior Driven Development approach is my favorite because it sets the best mindset: it builds on top of TDD focusing on business/user goals and drive towards shared understanding through conversations. In other words, it’s a high-level spec that fosters communication. When we read the test cases out loud, it feels like normal speech so it fits snugly into our minds. Good software is software that fits in our heads, which is easier and faster to create good mental models about it.

Our acceptance criteria are already written in BDD style, so the devs – we – have a perfect guide to start with.

Starting from the bottom

For this particular case, I think starting from the bottom/leave components will be easier since it’s basically the list-group component from Bootstrap. We won’t need to implement all the described features – but make sure to get familiar with it.

We quickly realize that list-group is composed of multiple list-group-item‘s. A list of items! So the leaf component should be <ListGroupItem>. Since the two are closely related, let’s keep their implementation in the same file.

So we create the two files and fire up the tests:

mkdir src/components
touch src/components/listGroup.js
touch src/components/listGroup.spec.js

Don’t forget to add // @flow.

I’ve picked Airbnb’s Enzyme test renderer because it has the necessary functionality for writing the kind of tests I prefer the most. Notably, Enzyme’s got a few pitfalls to be avoided. I’ll explain why I still use shallow soon enough.

To add the tools we need:

yarn add -D enzyme enzyme-adapter-react-16 jest-enzyme && yarn flow-typed install [email protected]

And add the new file src/setupTests.js:

// @flow
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import 'jest-enzyme';

configure({ adapter: new Adapter() });

A Component’s purpose

We start by describing what we’re testing: the new <ListGroupItem /> component. Its sole purpose is to return valid JSX, compliant with the component contract. The HTML + CSS defined by our external dependency, Bootstrap, is the contract that our React component must fulfill. It should be fairly simple to test because our component is just a pure function.

For now, Enzyme’s shallow is the right tool for the job.

// @flow
import React from 'react';
import { shallow } from 'enzyme';
import ListGroupItem from './ListGroup';

describe('<ListGroupItem />', () => {
  it('renders according to specification', () => {
    shallow(<ListGroupItem />)
  });
});

When we run the test watcher…

yarn test

… we get our first expected red of the cycle! This is obvious since there is no implementation yet. Keeping in mind the first rule of TDD, we implement our component to make this test pass:

// @flow
import React from 'react';

function ListGroupItem () {
  return null
}

export ListGroupItem;

Now we’re in the first expected green! Doesn’t that feel good? Let’s restart the cycle and get into red again by testing something more useful. The next step is to validate the generated structure. Red:

it('renders according to specification', () => {
  expect(
    shallow(<ListGroupItem />)
  ).toContainExactlyOneMatchingElement('li.list-group-item');
});

The test code above takes advantage of the cool matchers from jest-enzyme to make our code more readable. To make it green, the implementation must return the proper markup:

function ListGroupItem () {
  return <li className='list-group-item' />
}

The specification determines that every list item can have a string or an anchor as children. Let’s start with the string and assume it’s required. Red:

describe('<ListGroupItem />', () => {
  it('renders according to specification', () => {
    const wrapper = shallow(<ListGroupItem>item 1</ListGroupItem>);

    expect(wrapper).toContainExactlyOneMatchingElement('li.list-group-item');
    expect(wrapper).toHaveText('item 1');
  });
});

The implementation is more interesting now: in order to guarantee that the component is used correctly, we have to validate the given props. Flow comes in handy for this task. Green:

type ListGroupItemProps = {
  children: string,
};

function ListGroupItem (props: ListGroupItemProps) {
  return <li className="list-group-item">{props.children}</li>;
}

Note that the Jest watcher does not run Flow on file changes so type errors won’t break the tests. You can use flow-watch so Flow runs in every file change. Or, if you’re using an IDE, adding a Flow extension will come in handy.

This is enough features for this component for now. Let’s move on to the next component.

Red, Green, Red, Green, …

<ListGroup> is the parent component that has many <ListGroupItem>s. Just to save some time, here’s the whole component red tests:

describe('<ListGroup />', () => {
  it('renders according to specification', () => {
    const wrapper = shallow(
      <ListGroup>
        <ListGroupItem>item 1</ListGroupItem>
        <ListGroupItem>item 2</ListGroupItem>
      </ListGroup>
    );

    expect(wrapper).toContainExactlyOneMatchingElement('ul.list-group');
    expect(wrapper.find(ListGroupItem).length).toEqual(2);
  });
});

Now we have to put both components working together and since an empty list item didn’t make sense, a list group without list items doesn’t either. Green:

type ListGroupProps = {
  children: React.ChildrenArray<React.Element<typeof ListGroupItem>>
};

export function ListGroup (props: ListGroupProps) {
  return <ul className='list-group'>{props.children}</ul>
}

In the shallows, shallows…

shallow rendering is a powerful tool and having its documentation close by is quite handy. Never using it is a missed opportunity. Facebook has some more insights about it. Here are some reasons why I believe it’s the right tool for the component tests above:

  1. The components are just pure functions, no interactions with real DOM or higher-order components.
  2. Unit tests should be fast and shallow is faster than mount and render.
  3. Unit tests should be isolated. For <ListGroupItem> it’s easy because it’s completely independent but <ListGroup> depends on <ListGroupItem> as defined by the children argument type. Using mount/render would break the isolation of the component test because it renders the children.

Facebook’s react-dom/test-utils and react-test-renderer are testing libraries used by Enzyme. Which means that all the render stack from our test environment is mocked. The final result can be fully validated only when the code runs in a web browser. We’ll add another tool for validating components on the browser in another post.

Phew! What a ride. Thanks for reading all the way here. I really appreciate it. The series is not over though: the feature is far from done and the “leave components” are not completed yet.

Stay tuned for the next post in the series.
Cheers!

P.S.: All together now! In the shallow, shallow…

Confident React App – Part 2

This is the second post in the series where I show how I build up the confidence in the source code of my React apps. Please read the first post before continuing.

Communications is the key

A study of public GitHub issues found out that 78% of all software bugs are caused by specification error. Take a moment to let that sink in… That’s a big percentage! Basically, the quality of your team’s communication skills is by far the best investment you can do in order to reduce bugs in any application. The same research shows that type checking, either with Flow or TypeScript account only for ~15% of bug prevention.

So make sure your team/organization strive for creating and fostering a culture of safe and effective communication. People should communicate using a ubiquitous language which helps clarify specific jargons, contexts, and symbols. Not to mention that even improving formal language skills can have a positive impact. There’s nothing more powerful than a person who’s articulated and can think and speak.

Also the tools for communication should not be neglected. Wiki, Slack, whiteboards, Jira, Post-its, sharpies, company phones, video conference rooms and what-not are means to an important end.

Understanding the feature

We would like to quickly see the GitHub issues reported in our enterprise software in an integrated fashion so that we can find the necessary information fast; without switching context.

Our Customers

To achieve the desires of our customers, our (imaginary) multi-disciplinary development team has done research and a lot of discussions. Afterward, we came up with the following acceptance criteria written in our own syntax/DSL:

* Github integration *
  When the user is in the issues screen
    it must show a loading indicator while repo data is being fetched
    and repo data is available
      it must show a list of repos names
      it must show a summary of all open issues
      and there are open issues in a repo
        it must show the count of opened issues next to the repo name
      and when a repo is clicked
        and it is NOT selected
          it selects the clicked item
          and there ARE open repo issues
            it replaces the summary with the list of open repo issues
          and there ARE NOT open repo issues
            it shows the no issues message
        and it IS selected
          it deselects the clicked item
          it replaces the list of open repo issues with the summary

The trick for reading the above is that indentation must be taken into account. All sentences are appended to its parent group. Sentences on the same level of indentation are not appended. The sentences usually start with setting some pre-conditions (e.g.: data is available) or user behavior (e.g.: clicking an item) and they finish with some sort of outcome/side effect. (e.g.: selects the clicked item).

Here’s a complete example:
When the user is in the issues screen and repo data is available and a repo is clicked and it is NOT selected it selects the clicked item.

It is a bit weird at first but our team likes it and it works for us. Gherkin is a very popular choice if you don’t want to roll your own DSL.

Component specification

Our designers opted for using Twitter Bootstrap as the base of the app GUI and for the feature we’ll need the List Group component. The component’s documentation serves as specs and acceptance criteria for the React implementation that we’ll provide.

One of the variations of Bootstrap’s list-group component

So go back and read the acceptance criteria for the feature and the List Group specification. Try to imagine more use case scenarios or edge cases. Can you rewrite the sentences in a more clear way? Have you spoted an important missing thing?

In the next post we’ll dive into coding, I promise. But it will come with a twist.

Cheers!

Confident React App – Part I

Kent C. Dodds has written one of the coolest pieces about software testing that I’ve read recently. It’s a great overview of the most common types of testing. The “CONFIDENCE you must have in the code you deliver” is an original light that he shines on the topic. Before you continue reading this, go read his article first. It’s well worth it!

OK, do you feel inspired too? Then let me explain and show you how I make my React codebases reliable and my confidence levels pretty high.

First step is to create our React app. I’ll use Create React App (CRA) since it’s super simple and still very popular.

npx create-react-app confident-react-app

Let’s start by fulfilling the base of Kent’s Testing Trophy requirements. For that we use a bunch of tools that will save us from most of trivial JS mistakes.

Linting

CRA uses ESLint by default which is great. From ESLint’s website: “JavaScript, being a dynamic and loosely-typed language, is especially prone to developer error. Without the benefit of a compilation process, JavaScript code is typically executed in order to find syntax or other errors. Linting tools like ESLint allow developers to discover problems with their JavaScript code without executing it.” And here’s the exact ESLint configuration used by CRA. You can see that the configuration also applies accessibility linting rules which is a really nice bonus. It helps us create code that respects some of the impaired internet users.

Static type checking

JS is a dynamic language and experienced developers can really take advantage of dynamic types and objects. But even experienced developers make silly mistakes, work with other less experienced devs and most of a JS codebase relies on static types anyway. So a codebase that uses a strong type system can benefit from detecting type error mistakes quite early. Facebook created and uses Flow, a static type checker for javascript.

OK, Flow is intrusive. In order to benefit from it, your code will look quite similar to TypeScript. To be honest, if you’re doing a small library/app and even working with other developers with limited experience, adopting Flow/TypeScript will probably not be worth it due to the added entropy. It’s a part of the nature of being a JS developer, you get burned many times until you finally learn and embrace. I personally got so used to it that most of the times I find super annoying to write so much type boilerplate.

Which one should you choose? That’s an ever going debate that I don’t know the answer to. I recommend that you try both and get the feeling for yourself. This kind of choice is just too hard to be impartial to take.

For this series of posts, lets assume we’re building an enterprise multi-national application in a team with dozens of developers spread across the globe. We’ll take Flow to the next level by using it in strict mode. From now on, every JS file must have // @flow in its first line, even tests.

yarn add flow-bin && yarn flow init

If you run the flow command it should report errors on the tests since it doesn’t recognize Jest’s global functions – itdescribe, etc. To fix this you must inform Flow that Jest is a library with all its interface definitions. Thankfully there’s flow-typed which is a tool for installing community-supported library interface definitions.

yarn add -D flow-typed && yarn run flow-typed install [email protected]

Unfortunately these tools do not run by default with CRA’s own scripts. Let’s validate our code for every action, so this how we setup the scripts in our package.json:

"scripts": {
    "start": "yarn validate && react-scripts start",
    "build": "yarn validate && react-scripts build",
    "test": "yarn validate && react-scripts test",
    "validate": "yarn lint && yarn flow",
    "lint": "eslint src/**/*.js",
    "flow": "flow",
    "flow-typed": "flow-typed"
  }

Phew! A lot of setup and we haven’t written any line of code yet. We deserve a break. Building up the confidence in your codebase is not so simple and can be hard to build – just like in real life – but the return on the investment will be worth it.

You can find the source code on Github.

Next post we’ll talk about what we’ll be building and how.

Cheers!