pytest - using patch as a decorator to patch a constant

Viewed 1062

I have an object MyObject which uses a constant defined in mymodule.constants.MYCONSTANT. I can successfully patch the constant using a context manger like so:

import pytest
from unittest.mock import patch

def test_constant(self):
    with patch('mymodule.constants.MYCONSTANT', 3):
        MyObject()

However, I can't figure out how to use the equivalent patching using patch as a decorator:

@patch('mymodule.constants.MYCONSTANT', 3)
def test_constant(self, mock_constant):
     MyObject()

The above fails with a fixture mock_constant not found error. I tried using

@patch('mymodule.constants.MYCONSTANT', return_value=3)

But MYCONSTANT doesn't get replaced with the value of 3.

2 Answers

This is aligned to the behavior as documented:

unittest.mock.patch(target, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs)

... If patch() is used as a decorator and new is omitted, the created mock is passed in as an extra argument to the decorated function.

So, if you want to pass the extra argument to the decorated function, don't set the new argument:

@patch('mymodule.constants.MYCONSTANT')
def test_constant(self, mock_constant):
    ...

Setting the new argument means no extra argument would be passed:

@patch('mymodule.constants.MYCONSTANT', 3)
def test_constant(self):
    ...

Guys the order of parameters matter so first mocks then fixtures, otherwise it will still throw you the same error.

@patch('server.app.UserValidator')
def test_first_mocks(self,mock_user_validator:MagicMock,client_fixture:FlaskClient):
    # Arrange
    input = {'password':'internal','username':'error'}
    mock_user_validator.side_effect = ValueError('testing')
    # Act
    response = client_fixture.post('/api/users',json=input)
    # Assert
    mock_user_validator.assert_called_once()
    assert response.status_code == 500
Related