[PYTHON] Use Mock with pytest

Introduction

When I wrote the test code using pytest, I had a hard time using Mock, so I summarized it.

What is Mock

Mock is a module that facilitates unit testing on behalf of specific objects.

Preparation

  1. Install pytest

    pip install pytest pytest-mock
    
  2. Create code to test

Directory structure

```bash
root
├─src
│   └─script.py ・ ・ ・ Source code to be tested
└─tests
    ├─__init__.py ・ ・ ・ empty file
    └─script.py ・ ・ ・ Test code
```

Source code to be tested

```python:src/script.py
import requests


def sample1(url):
    return sample2(url)


def sample2(url):
    try:
        response = requests.get(url)
        return response.status_code
    except Exception:
        return 0


class SampleClass:
    def __init__(self):
        self._req = requests

    def get(self, url):
        try:
            response = self._req.get(url)
            return response.status_code
        except Exception:
            return 0
```

Performing tests using Mock

Mock the function

Replace the sample2 function when testing the sample1 function.

Specify the return value of the function

Test code

tests/test_script.py


from src.script import sample1


def test_sample1_mock_sample2_200(mocker):
    """
Make the sample2 function return 200.
    """
    status_code = 200
    url = "https://hogehoge.com"
    mocker.patch("src.script.sample2", return_value=status_code)
    assert sample1(url) == status_code


def test_sample1_mock_sample2_404(mocker):
    """
Make the sample2 function return 404.
    """
    status_code = 404
    url = "https://fugafuga.com"
    mocker.patch("src.script.sample2", return_value=status_code)
    assert sample1(url) == status_code

Execution result

 > pytest
======================== test session starts ========================
platform win32 -- Python 3.8.2, pytest-5.4.2, py-1.8.1, pluggy-0.13.1
rootdir: C:\root
plugins: cov-2.10.0, mock-3.3.1
collected 2 items

tests\test_script.py ..                                        [100%] 

========================= 2 passed in 0.19s =========================

Dynamically change the return value of a function

Test code

tests/test_script.py


from src.script import sample1


def test_sample1_mock_sample2(mocker):
    """
sample2 function is 200 by url,404,Try to return one of 0.
    """
    def return_status_code(url):
        """
200 by url,404,Returns one of 0
        """
        if url == "https://hogehoge.com":
            return 200
        elif url == "https://fugafuga.com":
            return 404
        else:
            return 0

    mocker.patch("src.script.sample2", side_effect=return_status_code)
    assert sample1("https://hogehoge.com") == 200
    assert sample1("https://fugafuga.com") == 404
    assert sample1("https://higehige.com") == 0

Execution result

 > pytest
======================== test session starts ========================
platform win32 -- Python 3.8.2, pytest-5.4.2, py-1.8.1, pluggy-0.13.1
rootdir: C:\root
plugins: cov-2.10.0, mock-3.3.1
collected 1 item

tests\test_script.py .                                         [100%] 

========================= 1 passed in 0.20s =========================

Raise an Exception when calling a function

Test code

tests/test_script.py


import pytest
from src.script import sample1


def test_sample1_mock_sample2_exception(mocker):
    """
Raise an Exception when the sample2 function is called.
    """
    url = "https://hogehoge.com"
    mocker.patch("src.script.sample2", side_effect=Exception)
    with pytest.raises(Exception):
        sample1(url)

Execution result

 > pytest
======================== test session starts ========================
platform win32 -- Python 3.8.2, pytest-5.4.2, py-1.8.1, pluggy-0.13.1
rootdir: C:\root
plugins: cov-2.10.0, mock-3.3.1
collected 1 item

tests\test_script.py .                                         [100%] 

========================= 1 passed in 0.17s =========================

Mock the module's function

Replace the get function in the request module when testing the sample2 function.

Specify the return value of the module function

Test code

tests/test_script.py


import requests
from src.script import sample2


def test_sample2_mock_requests_200(mocker):
    """
requests executed by the sample2 function.Replace get.
Also, the return value is also status in Mock_Replace code so that it returns 200.
    """
    status_code = 200
    url = "https://hogehoge.com"
    response_mock = mocker.Mock()
    response_mock.status_code = status_code
    mocker.patch.object(requests, "get", return_value=response_mock)
    assert sample2(url) == status_code


def test_sample2_mock_requests_404(mocker):
    """
requests executed by the sample2 function.Replace get.
Also, the return value is also status in Mock_Replace code so that it returns 404.
    """
    status_code = 404
    url = "https://fugafuga.com"
    response_mock = mocker.Mock()
    response_mock.status_code = status_code
    mocker.patch.object(requests, "get", return_value=response_mock)
    assert sample2(url) == status_code

Execution result

 > pytest
======================== test session starts ========================
platform win32 -- Python 3.8.2, pytest-5.4.2, py-1.8.1, pluggy-0.13.1
rootdir: C:\root
plugins: cov-2.10.0, mock-3.3.1
collected 1 item

tests\test_script.py ..                                        [100%] 

========================= 2 passed in 0.22s =========================

Raise an Exception when calling a module function

Test code

tests/test_script.py


import requests
from src.script import sample2


def test_sample2_mock_request_exception(mocker):
    """
requests with sample2 function.Raise an Exception when get is called.
    """
    status_code = 0
    url = "https://hogehoge.com"
    mocker.patch.object(requests, "get", side_effect=Exception)
    assert sample2(url) == status_code

Execution result

 > pytest
======================== test session starts ========================
platform win32 -- Python 3.8.2, pytest-5.4.2, py-1.8.1, pluggy-0.13.1
rootdir: C:\root
plugins: cov-2.10.0, mock-3.3.1
collected 1 item

tests\test_script.py .                                         [100%] 

========================= 1 passed in 0.19s =========================

Mock a function called within a method of a class

When testing the get method of the SampleClass class, replace the get function of _req called inside.

Specifies the return value of a function called within a class method

Test code

tests/test_script.py


from src.script import SampleClass


class TestSampleClass:
    def test_get_200(self, mocker):
        """
Called by get method of SampleClass_req.Replace get.
Also, the return value is also status in Mock_Replace code so that it returns 200.
        """
        status_code = 200
        url = "https://hogehoge.com"
        sample = SampleClass()
        response_mock = mocker.Mock()
        response_mock.status_code = status_code
        req_mock = mocker.MagicMock()
        req_mock.get = mocker.Mock(return_value=response_mock)
        mocker.patch.object(sample, "_req", req_mock)
        assert sample.get(url) == status_code

Execution result

 > pytest
======================== test session starts ========================
platform win32 -- Python 3.8.2, pytest-5.4.2, py-1.8.1, pluggy-0.13.1
rootdir: C:\root
plugins: cov-2.10.0, mock-3.3.1
collected 1 item

tests\test_script.py .                                         [100%] 

========================= 1 passed in 0.21s =========================

Raise an Exception when calling a function called within a class method

Test code

tests/test_script.py


from src.script import SampleClass


class TestSampleClass:
    def test_get_exception(self, mocker):
        """
With the get method of SampleClass_req.Raise an Exception when get is called.
        """
        status_code = 0
        url = "https://hogehoge.com"
        sample = SampleClass()
        req_mock = mocker.MagicMock()
        req_mock.get = mocker.Mock(side_effect=Exception)
        mocker.patch.object(sample, "_req", req_mock)
        assert sample.get(url) == status_code

Execution result

 > pytest
======================== test session starts ========================
platform win32 -- Python 3.8.2, pytest-5.4.2, py-1.8.1, pluggy-0.13.1
rootdir: C:\root
plugins: cov-2.10.0, mock-3.3.1
collected 1 item

tests\test_script.py .                                         [100%] 

========================= 1 passed in 0.21s =========================

Summary

I just researched various things and tried it myself, so I don't know if it's the correct way to use it.

Recommended Posts

Use Mock with pytest
Use mecab-ipadic-neologd with igo-python
Use RTX 3090 with PyTorch
Use pipdeptree with virtualenv
[Python] Use JSON with Python
Use indicator with pd.merge
Use Gentelella with django
I want to mock datetime.datetime.now () even with pytest!
Tested with boto3 + mock
Use mecab with Python3
Use tensorboard with Chainer
Use DynamoDB with Python
Use pip with MSYS2
Use Python 3.8 with Anaconda
Use pyright with Spacemacs
Use python with docker
Use TypeScript with django-compressor
Use LESS with Django
Use MySQL with Django
Use Enums with SQLAlchemy
Use tensorboard with NNabla
Use GPS with Edison
Use nim with Jupyter
Use Trello API with python
Use shared memory with shared libraries
Use "$ in" operator with mongo-go-driver
Use custom tags with PyYAML
Use directional graphs with networkx
Use TensorFlow with Intellij IDEA
Use Twitter API with Python
Try Google Mock with C
Use pip with Jupyter Notebook
Unit test flask with pytest
Use DATE_FORMAT with SQLAlchemy filter
Test standard output with Pytest
Use TUN / TAP with Python
Use sqlite3 with NAO (Pepper)
Use sqlite load_extensions with Pyramid
Use Windows 10 fonts with WSL
Use chainer with Jetson TK1
Use SSL with Celery + Redis
Use Cython with Jupyter Notebook
Use Maxout + CNN with Pylearn2
Use WDC-433SU2M2 with Manjaro Linux
Use OpenBLAS with numpy, scipy
Use subsonic API with python3
Use Sonicwall NetExtener with Systemd
Use prefetch_related conveniently with Django
Use AWS interpreter with Pycharm
Use Bokeh with IPython Notebook
Use Python-like range with Rust
Use MLflow with Databricks ④ --Call model -
Use pyright with CentOS7, emacs lsp-mode
Python: How to use async with
Use Azure SQL Database with SQLAlchemy
Use PointGrey camera with Python (PyCapture2)
Use vl53l0x with Raspberry Pi (python)
Use PX-S1UD / PX-Q1UD with Jetson nano
Mock in python-how to use mox
Use the preview feature with aws-cli
How to use virtualenv with PowerShell