How build testable React components?
Tags: typescript, react, javascript, testing
Published: 2020-07-19 04:23:43

Through the years software industry has created many patterns and practices that make the software we write both more flexible and easy to test and that includes our React components, with this in mind let's begin describing the problem.

*Note that these examples were written using Typescript since use types and interfaces make easier to see the concept, but you can write it in plain Javascript as well.*

Imagine that you have code like this:

**userClient.ts**
```typescript
class UserClient {
  getAll(): Promise<User[]> {
    // ... get users fetching them from an API
  }
}
```

**UserList.tsx**
```typescript
const userClient = new UserClient();

const UserList: React.FC = () => {
  const [users, setUsers] = React.useState<User[]>([]);

  React.useEffect(() => {
   userClient.getAll().then((usersList) => setUsers(usersList));
  }, []);
  
  return (
   <ul>
    {users.map(user => (
     <li key={user.id}>{user.name}</li>
    ))}
   </ul>
  )
}
```

Here we have a problem since testability perspective, as we are using a concrete instance of UserClient every time we run our UserList tests, we will be making a petition to fetch our users which for a test is not a good idea because your tests should be able to run even if your network connection is down or if the service where the data comes from is failing, so how do we solve this?

With this in mind we'll introduce the first important concept which it's **Dependency injection**

## Dependency Injection

Dependency injection is a pattern where you move the creation of the objects we depend on outside our current class/function/component where this object is called dependency, so, how does this looks like?

**UserList.tsx**
```typescript
interface Props {
  userClient: UserClient;
}

const UserList: React.FC<Props> = ({ userClient }) => {
  const [users, setUsers] = React.useState<User[]>([]);

  React.useEffect(() => {
   userClient.getAll().then((usersList) => setUsers(usersList));
  }, []);
  
  return (
   <ul>
    {users.map(user => (
     <li key={user.id}>{user.name}</li>
    ))}
   </ul>
  )
}
```

In the interface we defined for our component, we said that we will be receiving a prop called userClient of type UserClient, in this way we derived the responsibility to create the concrete object to the context where the component is rendered

```typescript
<UserList userClient={new UserClient()} />
```

This is part of the solution because since our tests we begin to think in the possibility to pass into our component a fake userClient instance which help us with our tests but how do we get that?

## Dependency Inversion 

Right now if take a look in how our component relate with UserClient we would see something like this:
![Relationship](https://dev-to-uploads.s3.amazonaws.com/i/nc6nr8shrq930bhmzq0h.png)

We can notice in this diagram that UserList depends on a concrete instance of UserClient, which it means that whatever this instance does it will have impact in our component even if that means to perform http petitions not only in our normal flow but in our test too, for this problem is where dependency inversion comes to help us.

Dependency inversion is a technique which allows us to decouple one object from another one, in this case our component and our userClient and decouple as its name says is reduce coupling, coupling in software engineering is how much one object knows about another one, the common desired is reduce the coupling because this makes our code more flexible, testable and maintainable. And we get this depending in **interfaces** rather than in concrete implementations.

First we create a interface for our class UserClient
**userClient.ts**
```typescript
interface IUserClient {
  getAll(): Promise<User[]>;
}

class UserClient implements IUserClient {
  getAll(): Promise<User[]> {
    // ... get users
  }
}
```

Doing this our class UserClient begins to depend in how IUserClient interface says that it should looks like, enforcing how our class should looks like we can guarantee that those places where we rely in that interface are gonna behave in a consistent way no matter which object we pass in as long as implements that interface.

**UserList.tsx**
```typescript
interface Props {
  userClient: IUserClient;
}

const UserList: React.FC<Props> = ({ userClient }) => {
  const [users, setUsers] = React.useState<User[]>([]);

  React.useEffect(() => {
   userClient.getAll().then((usersList) => setUsers(usersList));
  }, []);
  
  return (
   <ul>
    {users.map(user => (
     <li key={user.id}>{user.name}</li>
    ))}
   </ul>
  )
}
```

Unlike in how we did it before, we are relying in the interface IUserClient rather than the concrete implementation that UserClient is, so our relationship looks something like this:

![Dependency Inversion](https://dev-to-uploads.s3.amazonaws.com/i/lmkozpj2s3oxhhxjozy0.png)

We can see how now we are not depending in our class UserClient anymore but we are relying in objects which has the shape that IUserClient dictates, so in our tests we can do this

**UserList.test.tsx**
```typescript
class FakeUserClient implements IUserClient {
  getAll(): Promise<User[]> {
    return Promise.resolve([{}, {} ]) // Array of users
  }
}

test('Tests for UserClient component', () => {
  render(<UserList userClient={new FakeUserClient()} />)
  // Assertions
});
```

As we can see, we are not depending anymore in UserClient but in IUserClient interface, we can now use in our component any object as long as implements IUserClient, that's why we can in FakeUserClient class simulate our users petition and avoid them in our tests, making our test faster and independent from network conditions.

I hope you enjoyed it <3, please let me know any doubts you have.