Understanding different types of test

First, let's learn about the different types of tests and how they all fit into our project's workflow. The first thing to note is that some tests are more technically-focused, while others are more business-focused; some tests are only concerned with a very small part of the whole system, while others test the system as a whole. Here's a brief overview of the most common types of tests you'll encounter:

  • Unit tests: These test the smallest testable parts of an application, called units. For example, if we have a function called createUser, we can write a unit test that tests that the function always returns a promise.
    With unit tests, we are only concerned with the function of the unit, independent of external dependencies. If the unit has external dependencies, such as a database, we must substitute the real database client with a fake one. This fake client must be able to mimic the behavior of the database adequately so that, from the perspective of the unit under test, the fake behaves in the same way as the authentic database.
    We will talk more about fakes later, but the important takeaway is that unit tests test a small and specific component of the whole code base, using minimal (or no) dependencies, and without calling other parts of the application (that is, there are no side-effects).
  • Integration tests: These test whether different units can work together as a single, larger ensemble. To continue our example, the createUser function may rely on the Auth module to check whether the client has permission to create the user. We can create a test case where createUser is called with an unauthenticated client, and assert that the function throws an error.
    Integration tests test the integration between two or more units and ensure they are compatible. In our example, if the Auth module changes the data structure of its response payload, and we forget to update our createUser method to consume this new data structure, the integration test should fail, alerting us to fix it.
  • E2E/functional tests: These test the flow of an application from start to finish, acting as if we are the end consumers. In our example, we'd attempt to create a new user by actually sending a POST request to the /users endpoint, because that's how our end users would actually interact with our API. After the call, we'd check the database to ensure that a user document is indeed created and conforms to the expected data structure.
  • User interface (UI) tests: For applications that include a frontend component, UI tests are automated tests that mimic the behavior of real users interacting with the UI, such as scrolling and clicking. You may use generic browser automation tools such as Selenium (https://www.seleniumhq.org/), or framework-specific tools such as Enzyme (airbnb.io/enzyme/, used for React applications).
  • Manual tests: These are tests that cannot be automated. Manual tests should be kept to a minimum as they are not deterministic and there's a high cost to running them. Apart from catching bugs, manual tests can also unearth scenarios that are unintuitive and/or bad for user experience (UX).
  • Acceptance tests: These differ from the other tests that have already been outlined because they are more focused on business needs. They are a list of business requirements (as opposed to functional requirements), laid out by the business stakeholders, that the platform must fulfill. For example, one such requirement might read "95% of all visitors must be able to load the page within 3 seconds".
    This is not a purely technical requirement, but it drives the technical decisions that are to be made. For example, the development team may now be required to install analytics libraries to collect data on the load times of the site for all visitors, and to prioritize optimizing the site over developing new features.
    Parts of the acceptance tests may be written in a 
    Behavior-Driven Development (BDD) format, which focuses on the steps that an actual user may take when interacting with the platform. One such requirement may read "Given a user has successfully authenticated and he is on a product page, when he clicks the Add to Cart button, then that product should be added to the cart". Then, when this requirement is verified, either through automated and/or manual testing, it would pass the acceptance test.
    Think of acceptance tests as a final stage of the development process, when the business stakeholder accepts the work as complete.