This Article Is Worth Your Attention if You Are passionate about writing good quality software and want to enhance the stability of your app with tests. Are tired of unexpected bugs popping up in your production systems. Need help understanding what automated tests are and how to approach them. Why Do We Need Automated Tests? As engineers, we want to , but with each new feature we create, we inevitably increase the size and complexity of our apps. build things that work As the product grows, it becomes more and more time-consuming to (e.g., with your hands) test every functionality affected by your changes. manually The absence of automated tests leads to us either spending too much time and slowing our shipping speed down or spending too little to save the velocity, resulting in new bugs in the backlog along with the late-night calls from PagerDuty. On the contrary, . So, computers can be programmed to do the same repeatedly let's delegate testing to computers! Types of Tests The suggests . Let's dive deep into every kind and understand why we need each. Testing pyramid idea three main types of tests: unit, integration, and end-to-end Unit Tests A is a small piece of logic you test in (without relying on other components). unit isolation They finish within seconds. allows them to run them at any point in time, locally and on CI, without spinning up the dependent services/making API and database calls. Unit tests are fast. Isolation A function that accepts two numbers and sums them together. We want to call it with different arguments and assert that the returned value is correct. Unit test example: // Function "sum" is the unit const sum = (x, y) => x + y test('sums numbers', () => { // Call the function, record the result const result = sum(1, 2); // Assert the result expect(result).toBe(3) }) test('sums numbers', () => { // Call the function, record the result const result = sum(5, 10); // Assert the result expect(result).toBe(15) }) A more interesting example is the React component that renders some text after the API request is finished. We need to mock the API module to return the necessary values for our tests, render the component, and assert the rendered HTML has the content we need. // "MyComponent" is the unit const MyComponent = () => { const { isLoading } = apiModule.useSomeApiCall(); return isLoading ? <div>Loading...</div> : <div>Hello world</div> } test('renders loading spinner when loading', () => { // Mocking the API module, so that it returns the value we need jest.mock(apiModule).mockReturnValue(() => ({ useSomeApiCall: jest.fn(() => ({ // Return "isLoading: true" for this test case isLoading: true })) })) // Execute the unit (render the component) const result = render(<MyComponent />) // Assert the result result.findByText('Loading...').toBeInTheDocument() }) test('renders text content when not loading', () => { // Mocking the API module jest.mock(apiModule).mockReturnValue(() => ({ useSomeApiCall: jest.fn(() => ({ // Return "isLoading: false" for this test case isLoading: false })) })) // Execute the unit (render the component) const result = render(<MyComponent />) // Assert the result result.findByText('Hello world').toBeInTheDocument() }) Integration Tests When your interacts with other , we call it an . These tests are slower than unit tests, but they test how the parts of your app connect. unit units (dependencies) integration A service that creates users in a database. This requires a DB instance ( ) to be available when the tests are executed. We will test that the service can create and retrieve a user from the DB. Integration test example: dependency import db from 'db' // We will be testing "createUser" and "getUser" const createUser = name => db.createUser(name) // creates a user const getUser = name => db.getUserOrNull(name) // retrieves a user or null test("creates and retrieves users", () => { // Try to get a user that doesn't exist, assert Null is returned const nonExistingUser = getUser("i don't exist") expect(nonExistingUser).toBe(null); // Create a user const userName = "test-user" createUser(userName); // Get the user that was just created, assert it's not Null const user = getUser(userName); expect(user).to.not.be(null) }) End-to-End Tests It's an test when we test the , where all its dependencies are available. Those tests best simulate actual user behavior and allow you to catch in your app, but they are the type of tests. end-to-end fully deployed app all possible issues slowest Whenever you want to run end-to-end tests, you must provision all the infrastructure and make sure 3rd party providers are available in your environment. You want to have them for the features of your app. only mission-critical Login flow. We want to go to the app, fill in the login details, submit it, and see the welcome message. Let's take a look at an end-to-end test example: test('user can log in', () => { // Visit the login page page.goto('https://example.com/login'); // Fill in the login form page.fill('#username', 'john'); page.fill('#password', 'some-password'); // Click the login button page.click('#login-button'); // Assert the welcome message is visible page.assertTextVisible('Welcome, John!') }) How Do You Choose What Kind of Test to Write? Remember that , and tests. end-to-end tests are slower than integration integration tests are slower than unit If the feature you are working on is mission-critical, consider writing at least one test (such as checking how the Login functionality works when developing the Authentication flow). end-to-end Besides mission-critical flows, we want to test as many edge cases and various states of the feature as possible. allow us to test how the parts of the app work together. Integration tests Endpoints should perform the operations, produce the expected result, and not throw any unexpected errors. Having integration tests for endpoints and client components is a good idea. Client components should display the correct content and respond to user interactions with how you expect them to respond. And finally, when should we choose ? All the small functions that can be tested in isolation, such as that sums the numbers, that renders tag, are great candidates for unit tests. Units are perfect if you follow the approach. unit tests sum Button <button> Test Driven Development What's Next? (but start small) Write some tests! that suits your project/language. Each language has a popular library for testing, such as / for JavaScript, / for end-to-end (uses JavaScript as well), for Java, etc. Install a testing framework Jest Vitest Cypress Playwright JUnit Find a small function in your project and write a test for it. unit Write an test for some component/service-database interaction. integration Choose a critical scenario that can be quickly tested, such as a simple login flow, and write an test for that. end-to-end Do the things above once to understand how it works. Then, do it again during some feature/bug work. Then share it with your colleagues so that you all write tests, save time, and sleep better at night! Useful Resources: My personal blog by The Practical Test Pyramid Ham Vocke by Test Driven Development Martin Fowler