Categories
React

React Testing — Getting Started

Automated tests are important for most apps.

In this article, we’ll take a look at how to write tests for React components.

Testing Tools

Create React App has test files and scripts built into the project.

First Test

We can write our first test by adding test files in our Create React App project.

If we have App.js :

import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

In App.test.js , we write:

import React from 'react';
import { render } from '@testing-library/react';
import App from './App';

test('renders learn react link', () => {
  const { getByText } = render(<App />);
  const linkElement = getByText(/learn react/i);
  expect(linkElement).toBeInTheDocument();
});

to add the test.

The render function renders the App component that we imported.

Then we call getByText with a regex to get the element we’re looking for.

Finally, we call toBeInTheDocument to check linkElement is there.

Data Fetching

We add setup and teardown code with React tests.

Also, we can mock any HTTP requests in our test code so that we can run our tests in isolation.

For example, if we have the following component:

Answer.js

import React, { useState, useEffect } from "react";

export default function Answer(props) {
  const [data, setData] = useState({});

  async function fetchData() {
    const response = await fetch("https://yesno.wtf/api");
    setData(await response.json());
  }

  useEffect(() => {
    fetchData(props.id);
  }, []);

  return (
    <div>
      <p>{data.answer}</p>
    </div>
  );
}

Then we have to mock the Fetch API.

To do that, we can write:

Answer.test.js

import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";
import Answer from "./Answer";

let container = null;
beforeEach(() => {
  container = document.createElement("div");
  document.body.appendChild(container);
});

afterEach(() => {
  unmountComponentAtNode(container);
  container.remove();
  container = null;
});

it("renders answer data", async () => {
  const fakeData = {
    answer: "yes",
    forced: false,
    image: "https://yesno.wtf/assets/yes/6-304e564038051dab8a5aa43156cdc20d.gif"
  };
  jest.spyOn(global, "fetch").mockImplementation(() =>
    Promise.resolve({
      json: () => Promise.resolve(fakeData)
    })
  );

  await act(async () => {
    render(<Answer />, container);
  });

  expect(container.querySelector("p").textContent)
    .toBe(fakeData.answer);
  global.fetch.mockRestore();
});

In the code above, we have the container which we use to mount our component.

We have the beforeEach hook to create the container element to mount our component in.

And we have the afterEach hook to unmount our component and remove the container.

Then in our 'render answer data' test, we create the fakeData object with our mock response data.

Then we mock the fetch function in Answer.js by using the jest.spyOn method.

global is the global object, 'fetch' is the fetch function.

mockImplementation lets us mock fetch with an object with the json method that’s set to a function that returns a promise that resolves to the fake data.

Then we mount our Answer component with act and await it to let the promise resolve.

And then we check what’s in the container ‘s p element to check if it has what we expect.

Then finally, we call global.fetch.mockRestore() to clear the mocks.

Conclusion

We can add tests and mock any data fetching code with Jest and React’s test library.

Categories
React

React Testing — Timers, and Snapshots

Automated tests are important for most apps.

In this article, we’ll take a look at how to write tests for React components.

Timers

We can mock timers in our React component tests.

For example, if we have:

import React, { useEffect } from "react";

export default function Card({ onSelect }) {
  useEffect(() => {
    const timeoutID = setTimeout(() => {
      onSelect(null);
    }, 5000);
    return () => {
      clearTimeout(timeoutID);
    };
  }, [onSelect]);

  return (
    <>
      <button
        data-testid='button'
        onClick={() => onSelect(1)}
      >
        button
    </button>
    </>
  );
}

Then we can test it by writing the following:

Card.test.js

import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";

import Card from "./card";

jest.useFakeTimers();

let container = null;
beforeEach(() => {
  container = document.createElement("div");
  document.body.appendChild(container);
});

afterEach(() => {
  unmountComponentAtNode(container);
  container.remove();
  container = null;
});

it("should select null after timing out", () => {
  const onSelect = jest.fn();
  act(() => {
    render(<Card onSelect={onSelect} />, container);
  });

  act(() => {
    jest.advanceTimersByTime(100);
  });
  expect(onSelect).not.toHaveBeenCalled();

  act(() => {
    jest.advanceTimersByTime(5000);
  });
  expect(onSelect).toHaveBeenCalledWith(null);
});

it("should clean up on being removed", () => {
  const onSelect = jest.fn();
  act(() => {
    render(<Card onSelect={onSelect} />, container);
  });
  act(() => {
    render(null, container);
  });
  act(() => {
    jest.advanceTimersByTime(5000);
  });
  expect(onSelect).not.toHaveBeenCalled();
});

it("should accept selections", () => {
  const onSelect = jest.fn();
  act(() => {
    render(<Card onSelect={onSelect} />, container);
  });
  act(() => {
    container
      .querySelector("[data-testid='button']")
      .dispatchEvent(new MouseEvent("click", { bubbles: true }));
  });
  expect(onSelect).toHaveBeenCalledWith(1);
});

We have the usual beforeEach and afterEach hooks to create and remove the container for mounting our component for testing.

In the ‘should select null after timing out’, we set the timer to the time we want by calling the jest.advanceTimerByTime method.

Then we check what the mockedonSelect function is called with toHaveBeenCalledWith .

And we check if onSelect is called with toHaveBeenCalled .

In the “should clean up on being removed” test, we make sure that onSelect isn’t called after it’s unmounted.

In the “should accept selections” test, we triggered the click event on the button.

Then we check is onSelect is called with the argument we expect.

Snapshot Testing

We can test snapshots, which is the rendered component at a given time.

For example, if we have:

import React from "react";

export default function Hello({ name }) {
  return (
    <>
      <p>hello {name}</p>
    </>
  );
}

Then we can add the following test to test the component:

Hello.test.js

import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";
import pretty from "pretty";
import Hello from "./hello";

let container = null;
beforeEach(() => {
  container = document.createElement("div");
  document.body.appendChild(container);
});

afterEach(() => {
  unmountComponentAtNode(container);
  container.remove();
  container = null;
});

it("should render a greeting", () => {
  act(() => {
    render(<Hello />, container);
  });
  expect(pretty(container.innerHTML)).toMatchInlineSnapshot(`"<p>hello </p>"`);

  act(() => {
    render(<Hello name="james" />, container);
  });
  expect(pretty(container.innerHTML)).toMatchInlineSnapshot(
    `"<p>hello james</p>"`
  );
});

We have the same beforeEach and afterEach hooks as before.

To test the component, we get the rendered HTML with container.innerHTML .

Then we check it against the HTML code with toMatchInlineSnapshot .

We have to install pretty and prettier by running:

npm i pretty prettier --save-dev

to get the pretty function to render the HTML.

Conclusion

We can mock timers and test React components with rendered HTML.

Categories
React

React Testing — Mocking Modules and Dispatching Events

Automated tests are important for most apps.

In this article, we’ll take a look at how to write tests for React components.

Mocking Modules

We can mock modules that don’t work well in a test environment.

For example, if we have the following components:

Map.js

import React from "react";

import { LoadScript, GoogleMap } from "react-google-maps";
export default function Map(props) {
  return (
    <LoadScript id="script-loader" googleMapsApiKey="YOUR_API_KEY">
      <GoogleMap id="example-map" center={props.center} />
    </LoadScript>
  );
}

Contact.js

import Map from "./map";

export default function Contact({ name, email }) {
  return (
    <div>
      <address>
        {name} {email}
      </address>
      <Map center={props.center} />
    </div>
  );
}

Then we can test the Contact component with a mocked Map component by writing:

Contact.test.js

import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";

import Contact from "./contact";

jest.mock("./map", () => {
  return function DummyMap(props) {
    return (
      <div data-testid="map">
        {props.center.lat}:{props.center.long}
      </div>
    );
  };
});

let container = null;
beforeEach(() => {
  container = document.createElement("div");
  document.body.appendChild(container);
});

afterEach(() => {
  unmountComponentAtNode(container);
  container.remove();
  container = null;
});

it("should render contact information", () => {
  const center = { lat: 0, long: 0 };
  act(() => {
    render(
      <Contact
        name="james"
        email="test@example.com"
        center={center}
      />,
      container
    );
  });

  expect(
    container.querySelector("address").textContent
  )
    .toContain("james test@example.com");

  expect(container.querySelector('[data-testid="map"]').textContent).toEqual(
    "0:0"
  );
});

We have:

jest.mock("./map", () => {
  return function DummyMap(props) {
    return (
      <div data-testid="map">
        {props.center.lat}:{props.center.long}
      </div>
    );
  };
});

to mock the Map component.

Then when we call render , we render with DummyMap instead of the actual Map component.

Events

To test events, we can dispatch real DOM events on DOM elements.

For instance, if we want to test the Toggle component:

import React, { useState } from "react";

export default function Toggle(props) {
  const [state, setState] = useState(false);
  return (
    <button
      onClick={() => {
        setState(previousState => !previousState);
        props.onChange(!state);
      }}
      data-testid="toggle"
    >
      {state ? "off" : "on"}
    </button>
  );
}

Then we can add a test file for it by writing:

Toggle.test.js

import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";

import Toggle from "./toggle";

let container = null;
beforeEach(() => {
  container = document.createElement("div");
  document.body.appendChild(container);
});

afterEach(() => {
  unmountComponentAtNode(container);
  container.remove();
  container = null;
});

it("changes value when clicked", () => {
  const onChange = jest.fn();
  act(() => {
    render(<Toggle onChange={onChange} />, container);
  });
  const button = document.querySelector("[data-testid=toggle]");
  expect(button.innerHTML).toBe("on");
  act(() => {
    button.dispatchEvent(new MouseEvent("click", { bubbles: true }));
  });
  expect(onChange).toHaveBeenCalledTimes(1);
  expect(button.innerHTML).toBe("off");
  act(() => {
    button.dispatchEvent(new MouseEvent("click", { bubbles: true }));
  });
  expect(onChange).toHaveBeenCalledTimes(2);
  expect(button.innerHTML).toBe("on");
});

We mock the onChange method that we pass in as the value of the onChange prop.

Then we get the button with the selector[data-testid=toggle] from the Toggle component.

Then we can get the content of the button and how many times onChange has been called after we call dispatchEvent to dispatch a click MouseEvent .

We need to pass in { bubbles: true } so that React will delegate the event to the document.

Conclusion

We can mock modules that we can’t use conveniently in our tests.

Also, we can trigger events on elements and check the result after that.

Categories
React Native

React Native — JavaScript Environment, Network, and Security

React Native is a mobile development that’s based on React that we can use to do mobile development.

In this article, we’ll look at how to use it to create an app with React Native.

JavaScript Environment

React Native runs in an environment that lets us use modern JavaScript features.

In most cases, React Native lets us run with JavaScriptCore, which also powers Safari.

We can use most modern features like for-of, object spread, template literals, spread and rest, modules, async and await, and more.

Also, we can use popular methods like console.log , array methods, object methods, and more.

Timers and InteractionManager

We can use the InteractionManager object to run timers on a separate thread.

This is better than the alternative of using JavaScript timer functions like setTimeout and setInterval in our React Native code.

For example, we can write:

InteractionManager.runAfterInteractions(() => {
  // ...long-running synchronous task...
});

We run runAfterInteractions method with a callback and we can run any long-running synchronous code in the callback.

Networking

We can make HTTP requests with the Fetch API in our React Native app.

For example, we can write:

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

export default function App() {
  const [answer, setAnswer] = useState('');

  const fetchData = async () => {
    const res = await fetch('https://yesno.wtf/api');
    const { answer } = await res.json();
    setAnswer(answer);
  }

  useEffect(() => {
    fetchData();
  }, [])

  return (
    <View style={styles.container}>
      <Text>{answer}</Text>
    </View >
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

to make an HTTP request to an API.

We make a request when we load the app with the useEffect callback and an empty array for the 2nd argument.

WebSocket Support

React Native has WebSocket support.

To connect to a socket and listen to the connection, we can write:

const ws = new WebSocket('ws://host.com/path');

ws.onopen = () => {
  ws.send('something');
};

ws.onmessage = (e) => {
  console.log(e.data);
};

ws.onerror = (e) => {
  console.log(e.message);
};

ws.onclose = (e) => {
  console.log(e.code, e.reason);
};

The onopen method is run when we open the connection.

The onmessage method is run when a message is received.

onerror is run when an error occurred.

onclose method is run when the connection is closed.

Security

Like any other kind of apps, we’ve to think about security when we create our React Native app.

We should think about ways to store sensitive data.

To store environment variables, we can use the react-native-dotenv or react-native-config libraries.

We can use them to read things like API keys and other secrets from environment variables.

Secure Storage

To store data securely, we can use the Android Keystore to store items securely.

Also, we can use Encrypted Shared Preferences to store data.

Conclusion

We should think about security when we’re writing React Native apps.

Also, we can use the Fetch API to make HTTP requests from our React Native app.

Categories
React Native

React Native — Async Storage

React Native is a mobile development that’s based on React that we can use to do mobile development.

In this article, we’ll look at how to use it to create an app with React Native.

Async Storage

We can use the Async Storage library to store unencrypted data in our React Native app.

To install the library, we run:

yarn add @react-native-community/async-storage

Saving and Reading Data

Once we installed the library, we can import the library into our project and use it.

To do that, we write:

import React, { useState } from 'react';
import { View, StyleSheet, Text, Button } from 'react-native';
import AsyncStorage from '@react-native-community/async-storage';

export default function App() {
  const [data, setData] = useState();

const getData = async () => {
    try {
      const value = await AsyncStorage.getItem('@storage_Key')
      if (value !== null) {
        setData(value)
      }
    } catch (e) {
      // error reading value
    }
  }

  const storeData = async (value) => {
    try {
      await AsyncStorage.setItem('@storage_Key', value)
    } catch (e) {
      // saving error
    }
  }

  return (
    <View style={styles.container}>
      <Button title='save data' onPress={() => storeData('foo')}></Button>
      <Button title='read data' onPress={getData}></Button>
      <Text>{data}</Text>
    </View >
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

We import the AsyncStorage object.

And we use them in the getData function to get data and the storeData function to store the data.

The AsyncStorage.getItem method gets the data by its key.

And AsyncStorage.setItem takes the key and the value for the key and save the data into storage.

We can also write and read objects as value.

For example, we can write:

import React, { useState } from 'react';
import { View, StyleSheet, Text, Button } from 'react-native';
import AsyncStorage from '@react-native-community/async-storage';

export default function App() {
  const [data, setData] = useState();

  const getData = async () => {
    try {
      const value = await AsyncStorage.getItem('@storage_Key')
      if (value !== null) {
        setData(JSON.parse(value))
      }
    } catch (e) {
      // error reading value
    }
  }

  const storeData = async () => {
    try {
      await AsyncStorage.setItem('@storage_Key', JSON.stringify({ foo: 'bar' }))
    } catch (e) {
      // saving error
    }
  }

  return (
    <View style={styles.container}>
      <Button title='save data' onPress={storeData}></Button>
      <Button title='read data' onPress={getData}></Button>
      <Text>{data.foo}</Text>
    </View >
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

We called JSON.stringify to convert the object into a JSON string in storeData .

The getData function parses the stringified object that’s read from getItem with JSON.parse .

Remove Data

There’s also the removeItem method to remove data to remove an item by the key.

For example, we can write:

import React, { useState } from 'react';
import { View, StyleSheet, Text, Button } from 'react-native';
import AsyncStorage from '@react-native-community/async-storage';
export default function App() {
  const [data, setData] = useState();
  const getData = async () => {
    try {
      const value = await AsyncStorage.getItem('@storage_Key')
      setData(value);
    } catch (e) {
      // error reading value
    }
  }
  const storeData = async (value) => {
    try {
      await AsyncStorage.setItem('@storage_Key', value)
    } catch (e) {
      // saving error
    }
  }

  const removeValue = async () => {
    try {
      await AsyncStorage.removeItem('@storage_Key')
    } catch (e) {
      // remove error
    }
  }

  return (
    <View style={styles.container}>
      <Button title='save data' onPress={() => storeData('foo')}></Button>
      <Button title='read data' onPress={getData}></Button>
      <Button title='remove data' onPress={removeValue}></Button>
      <Text>{data}</Text>
    </View >
  );
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

We call removeItem with the key in the removeValue function to remove an item with the given key.

Conclusion

The async storage library gives us an easy way to store data in an unencrypted manner with React Native.