Mocking in Testing: from zero to hero

In end-to-end (E2E) automated testing, we attempt to simulate how the end-user would use the product—clicking on buttons, filling out…

Author Avatar
By Roy Segall,

In end-to-end (E2E) automated testing, we attempt to simulate how the end-user would use the product—clicking on buttons, filling out forms, etc.—to see if the UI produces the expected results. The product relies on many smaller pieces like React/Angular/Vue components or functions that process data from the database in the backend.

Testing those logic units is pretty tricky because they incur side effects, such as interacting with the DB or changing elements in the DOM. Mocking the responses is one possible solution, but what the heck is mocking? Without any further introduction – let’s dive in.

Mocks 101

Let’s assume that your application correctly pulls a dad joke from an API:

const axios = require('axios');
const {key} = require('./config.js')

function getJoke() {

    const options = {
        method: 'GET',
        url: 'https://dad-jokes.p.rapidapi.com/random/joke',
        headers: {
            'x-rapidapi-host': 'dad-jokes.p.rapidapi.com',
            'x-rapidapi-key': key
        }
    };

    return axios.request(options).then(function (response) {
        const {setup, punchline} = response.data.body[0];
        return `Your dad joke is: ${setup} - ${punchline}`
    }).catch(e => {
        console.log(e);
    });
}

If you’d like to see this in JSON or PHP, check this out

Now, you are pretty sure this unique app will help you land a deal in “Shark Tank,” but someone asks, “do you have automated tests?” Rather than mumbling a fake answer on national TV, let’s see how we can test it. Unlike the famous calculator example where we know that 2 + 2 = 4, the dad joke API can return any kind of value or maybe fail.

In unit-testing, we want to test the smallest piece of logic, and relying on a third-party vendor might cause a lot of false positives. One solution is to use mocks:

const axios = require('axios');
const {getJoke} = require('../get-joke');
jest.mock('axios');

describe('Get joke', () => {
    let requestMock = jest.fn();

    beforeAll(() => {
        axios.request.mockImplementationOnce(requestMock);
    });

    it('Should return a joke number and invoke with the correct values', async () => {
        requestMock.mockResolvedValue({
            data: {
                body: [{
                    setup: 'Why Was Six Afraid of Seven',
                    punchline: 'Because Seven ate Nine!'
                }]
            }
        });

        const joke = await getJoke();
        expect(joke).toBe('Your dad joke is: Why Was Six Afraid of Seven - Because Seven ate Nine!');
        expect(requestMock).toBeCalledTimes(1);
        expect(requestMock).toBeCalledWith({
            "method": "GET",
            "url": "https://dad-jokes.p.rapidapi.com/random/joke",
            "headers": {
                "x-rapidapi-host": "dad-jokes.p.rapidapi.com",
                "x-rapidapi-key": "your_key",
            },
        });
    });
});

To see this in JSON or PHP check this link

As we can see, mocks allow us to isolate a logic unit and test it without being affected by other factors. Let’s summarize how you can use Mocks:

  1. Control side effects – by controlling the results of functions, we can isolate our test to the parts we control and verify they are performing as expected.   
  2. Introduce chaos – usually, third-party services work well. Testing how a function works in chaos is good, but we can’t ask the service to shut down for our unit test. Controlling the side effect and returning a false value, or throwing an exception, will help us see how chaos affects the code.   

How mocks work behind the scene

I’ve been asking myself this question for quite some time. Since this one is pretty technical, I’ll try to simplify. When we import a function, we have a reference in the memory to that function. The mocking library will replace that reference with another object that will help us to test stuff we could not before:

  1. Invocation – We can ensure that a function was called, see how many times it was called, and with which arguments. This will ensure that we call a function correctly with the expected parameters.
  2. Return a value – just like the example above with the dad joke, we can return any value we want, thus isolating unexpected behavior of other services.

How do other languages handle mocking?

  1. One of Python’s most significant advantages is that mocking is part of the language, and there’s no need for external libraries that try to implement mocking in their way. 
  2. PHP has a test runner, PHPUnit, which became the standard in the community. The test runner provides a way to mock calls, but IMO, it’s not the best DX compared to Python.

Mocks in an E2E approach

Well…we can talk about unit tests until next Christmas but since Testim is an E2E platform, let’s talk about E2E and mocking. There’s only one problem – that approach is not widely common, and there is a good reason. In E2E testing, we want to test our application as it would perform IRL, and using mocks is going against the E2E paradigm.

But, in the past few years, new tools arrived and created a disruption in the E2E world – playwright and cypress – and one of the coolest features was the ability to mock networks. Testim provides that option as well.

But why do we want to mock the network anyway? 

First, we would like to mock the network for edge cases that are hard to simulate. Maybe we rely on dates (transition to/from daylight saving time). Or maybe our billion-dollar algorithm sends requests to the dad jokes API and raises an error that is hard to reproduce while doing E2E testing.

Second, we might want to sniff the network to ensure the correct response was sent with the right values. Maybe we want to verify we did not send requests above the number we allow. Testim has a built-in solution, and you can look here at how to use the feature in your tests.

Wrapping it up

After reading this blog post, I hope that you’ll know how to test your code better and cover more edge cases that you could not do previously and as always, practice makes better. As you write more tests with mocking, you’ll test earlier, better, and isolate potential bugs. It can make you a bigger testing rockstar than you already are.