100% test coverage is not enough
testing

100% test coverage is not enough…

Even if your unit tests cover everything and pass at build time, your app can still be completely broken in production. 🧐🤔

In web application development, testing is crucial. 
You can’t build a high-quality app without proper testing.

So today we will talk about TESTING. Hello, Mic, Testing 1,2,3,4 … 🎤

We test in one of two ways:

  • Manual
  • Automated

Manual Testing

As the name suggests, the testing is done manually in this case, by humans.
And we can’t deny it, we all do it. But that’s not what we’ll talk about today.

We will talk about Automated Testing 🤖

Automated Testing

There are three different methods of Automated Testing:

  • Unit
  • Integration
  • End-to-End

Let’s take a closer look at each approach.

Unit Testing
  • Unit tests take a piece of the product and test that piece in isolation.
  • Unit testing should focus on testing small units.
  • Units should be tested independently of other units. 
    This is typically achieved by mocking the dependencies.
Integration Testing
  • Integration testing is when we integrate two or more units.
  • An integration test checks their behavior as a whole, to verify that they work together coherently.
End-to-End Testing
  • End-to-end testing is a technique used to test whether the entire application flow behaves as expected from start to finish.
  • Tests that simulate real user scenarios can easily help to determine how a failing test would impact the user.

Wow, now we know a bit about what those three mean 👏

Now let’s dig into Unit Testing and the associated tools.

In the last few years, several tools have been popular, including:

What is Jest? (From its official site)

Jest is a delightful JavaScript Testing Framework with a focus on simplicity.
It works with projects using: Babel, TypeScript, Node, React, Angular, Vue and more!

For this article, we use React examples, but the approach would be similar in other frameworks.

First, let’s see how Jest works. We have a module called sum.js –

function sum(a, b) {
  return a + b;
}
module.exports = sum;

We can test this using Jest in sum.test.js like this:

const sum = require('./sum');

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});

And wow, it will pass. 👏 awesome!!!

But what happens, if we use null or "1" –

sum(null, 2);
sum("1", 2);
// etc...

You might try to add all the possible cases to test it and make it solid. But let’s be honest - most companies don’t research all the test cases for any unit.

But for the sake of simplicity, let’s assume this function is tested enough to work perfectly. So, we need to find out all the units of the app and test them in the same manner.

And then again, do we really know what the Units of the app are?

For example:

We don’t know the value of a and b for the sum function. They come from 2 separate modules.

function getA() {
  // calculations or Remote API call
  return a;
}
module.exports = getA;

function getB() {
  // calculations or Remote API call
  return b;
}
module.exports = getB;

So, to do the sum operation, we need to do something like this:

const a = getA();
const b = getB();
// then the sum op
sum(a, b);

But we are really motivated developers and want to prepare unit test for getA and getB as well as sum, so we think we have 100% Unit test coverage.

All our tests pass separately as units - but when we run the whole thing together in production, it still doesn’t work as expected.

Then we think of creating a function named doEverythingAndReturnSum which (as the name suggests) does everything and returns sum:

function doEverythingAndReturnSum() {
  const a = getA();
  const b = getB();
  // then the sum op
  sum(a, b);
}


Wow, nice.
–But hat is the Unit here?

getA ? getB ? sum? or doEverythingAndReturnSum ? 💭

Alt Text

If we now test all the units separately and feel happy that our app has 100% test coverage, the result might look something like this 🙈💥

So far, we’ve been looking mostly at the JavaScript code, not the user interface. But as we know, UI testing is even more challenging, as there are multiple layers of code involved:

  • DOM
  • styles
  • events
  • Data coming from Remote APIs
  • Browsers, and so on

When we talk about UI testing for React apps, the first thing that comes to mind is “Components”.
Components are the building blocks for a React app. 

  • So, of course, we need to test the components.

One of the most popular tools for testing React components is Enzyme.

Enzyme is a JavaScript Testing utility for React that makes it easier to test your React Components’ output. You can also manipulate, traverse, and in some ways simulate runtime given the output.

Let’s look at an example of how we can unit-test a component with Jest and Enzyme.

We have one component called MyComponent, and we pass a <div> element to it with a class named unique

To test it, we assert that after rendering, the component should include the <div> with the unique class:

import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';

import MyComponent from './MyComponent';

describe('<MyComponent />', () => {
  it('renders children when passed in', () => {
    const wrapper = shallow((
      <MyComponent>
        <div className="unique" />
      </MyComponent>
    ));
    expect(wrapper.contains(<div className="unique" />)).to.equal(true);
  });
});

Fair enough!

It passes, which means it has a <div> element and the unique class.

But wait…
–What if in the same Commit, I completely destroyed the styling of the unique class? This test will still pass 🤨

There is another way of testing React Components – Jest Snapshot testing. That’s also another unit testing mechanism which is not enough on its own. You can read my another post here Jest Snapshot Testing for React Components is useless? Is it slowly dying? 🧐🤔😐

Also, we may have hundreds of React Components in the project. (If your project is big enough, you could even have thousands). If we unit-test all the components separately, that doesn’t necessarily mean all the components will work together well.

Even if we ignore the remote API for a second, it still doesn’t guarantee that.

Oh, another fun thing:
–Our application is a web app and it will run in a browser (and even in multiple browsers – as we don’t know which browsers people will use).

But we aren’t testing components in a single browser yet. How can we guarantee that it will work properly in different browsers?

Long, long ago in 2015 (long ago, because in the web or JS world, 2015 is considered ancient times), Google posted an article on the Testing Pyramid:

Alt Text

They suggested to do mostly unit testing and not much end-to-end (E2E) testing: “Just Say No to More End-to-End Tests”.

Martin Fowler gave his thoughts on the TestPyramid even before in 2012:

Its essential point is that you should have many more low-level UnitTests than high level BroadStackTests running through a GUI.

Alt Text

This thought from Martin seems realistic, so you can actually take your decision based on your financial resources and team strength.

Kent C. Dodds merged these both and came up with this-

Alt Text

Awesome!

Also, he proposed what he calls the “Testing Trophy”, which focuses mostly on the integration testing.

Alt Text

With today’s web, I think we cannot rely on unit testing alone. It’s better to use a lot of integration testing to make the application solid.

As Guillermo Rauch said –

Alt Text

The “application” concept on the modern web is changing rapidly. The browser can do a lot more than before. JavaScript is evolving with ESNext. In fact, time has also changed the testing tools. Nowadays, we have more tools and frameworks to do integration and E2E testing in a much better way.

I plan to show how we can do integration and E2E testing in a better way for modern web applications in future articles.

Till then,
Cheers!
👋

As I am trying to contribute contents on the Web, you can buy me a coffee for my hours spent on all of these ❤️😊🌸

Buy Me A Coffee
Standard

Leave a Reply

Your email address will not be published. Required fields are marked *