pythonmockingpytestpytest-mock

Error in the patch of a function by mockers and pytest


I am trying to use pytest and mocker to test a python package that I am writing. This is the outline of my repo (assuming package is called hoopla)

hoopla
|- library
  |- __init__.py
  |- general
  |- exceptions
  |- bourhaha 
|- tests
  |- __init__.py
  |- test_brouhaha

Inside general.py I have some functions that can be used via the package and also files inside the package. For example:

Inside test_brouhaha.py I want to mock the validate_email_exists() call when testing the function create_username() as it calls out to an external system.
When I try to mock this call using pytest and pytst-mock I get an error saying No Module... (see below).

# general.py

def validate_email_exists(email):
  return True
# bourhaha.py

from .general import validate_email_exists

def create_username(email):
  if not validate_email_exists(email):
    return False
  # create usename
  return True
# test_bourhaha.py

from library.bourhaha import *

def test_create_username(mocker):
  mock_email_exists = mocker.patch("library.bourhaha.general.validate_email_exists") # causes error
---
  mock_email_exists = mocker.patch("library.general.validate_email_exists") # causes error
  mock_email_exists.return_value = False
  assert create_username("test") # Fails because value of validate_email_exists return True
---

In my code I initially mocked with

mock_email_exists = mocker.patch("library.brouhaha.general.validate_email_exists")
mock_email_exists.return_value = False

And this throws the error

ModuleNotFoundError: No module named 'library.brouhaha.general'; 'library.brouhaha' is not a package

When I try

mock_email_exists = mocker.patch("library.general.validate_email_exists")
mock_email_exists.return_value = False

There is no error however the test fails because the function is returning True


Solution

  • Where to patch

    The problem is in the path used by the patch() function. The correct path is:

    # CORRECT PATCHING
    mock_email_exists = mocker.patch("library.bourhaha.validate_email_exists")
    

    See where to patch in the documentation to know other details about a correct use of patch().

    In your case the file general.py defines the function validate_email_exists, but the file bourhaha.py execute the import instruction:

    from .general import validate_email_exists
    

    so it creates a name validate_email_exists in its namespace. You have to change the object pointed by this name. With your patch() you have changed the object in the place where it is defined.

    Back to your code

    In your code you set the return_value of mock_email_exists to False. At this point you have to also change the assert instruction because the function create_username() return False when the function validate_email_exists() return False.

    So the correct test function is:

    from library.bourhaha import *
    
    def test_create_username(mocker):
        # WATCH to the path = 'library.bourhaha.validate_email_exists'
        mock_email_exists = mocker.patch("library.bourhaha.validate_email_exists")
        mock_email_exists.return_value = False
        # I have change the assert test (assert ---> assert not)
        assert not create_username("test")
    

    Your modules general.py and bourhaha.py are perfect.