Skip to main content

Create unittest.mock.patch target values from python modules instead of string wrangling

Project description

Patch Target: A tiny library for creating valid unittest.mock.patch target arguments

The ability to monkey patch modules, functions, classes, and class instances is a powerful part of testing Python code; however, the unittest.mock.patch function depends on the developer providing a precise module path AS A STRING to the object to be patched. If you refactor where your modules live or make other changes, you will find the annoying and hard-to-decipher-at-first errors pointing to an invalid module path string.

This small library, consisting of a single exported function, attempts to make using this function a more straightforward, reliably correct experience.

the patch_target function takes 2 arguments:

  • a host_module of type ModuleType
  • and an object_to_be_patched, a Union of Named | Callable[[Any], Any] | str (the str type is used for overriding module-level attribute) where Named is a Protocol describing an object with a __name__ attribute.

Since you're dealing with python objects instead of strings, you get more guarantees out of the box. E.g. since you have to pass in a module instead of a string, that means you have to have successfully imported the module into your test to begin with

example:

Given a src code file like this:

# myapp.mymodule.mysubmodule

import datetime
from uuid import uuid4

my_module_level_attribute = "some_value"

def get_current_time():
    return datetime.datetime.now()

def generate_new_id():
    return uuid4()

You can patch the non-deterministic pieces (current time and uuid generation) like so:

from unittest.mock import patch, Mock
from myapp.my_module import my_submodule  # noqa
import datetime

import uuid

from patch_target import patch_target


# using the patch decorator to override a module imported into the host_module
@patch(patch_target(my_submodule, datetime))   # Using string paths the patch arg would be  "myapp.mymodule.my_submodule.datetime"
def test_get_current_time(mock_datetime: Mock) -> None:
    the_beginning_of_time = datetime.datetime(1970, 1, 1)
    mock_datetime.datetime.now.return_value = the_beginning_of_time
    
    actual = my_submodule.get_current_time()
    expected = the_beginning_of_time

    assert actual == expected


# using the patch context manager override a function imported into the host_module
def test_generate_new_id() -> None:
    fake_id = "my-super-cool-id"
    with patch(patch_target(my_submodule, uuid.uuid4)) as mock_uuid4: # Using string paths the patch arg would be  "myapp.mymodule.my_submodule.uuid.uuid4"
        mock_uuid4.return_value = fake_id
        
        actual = my_submodule.generate_new_id()
        expected = fake_id
        
        assert actual == expected


# using the patch context manager override a module_attribute defined in the host_module
def test_get_module_level_attribute() -> None:
    with patch(patch_target(my_submodule, "my_module_level_attribute"), "some_other_value"): # Using string paths the patch arg would be  "myapp.mymodule.my_submodule.my_module_level_attribute"        
        actual = my_submodule.my_module_level_attribute
        expected = "some_other_value"
        
        assert actual == expected

Note that though patching module-level attributes using the string name of the variable is supported, you can just use unittest.mock.patch.object the same way. So you don't need this lib to accomplish it.

# using unittest.mock.patch.object
from unittest.mock import patch

from myapp.my_module import my_submodule  # noqa

def test_get_module_level_attribute() -> None:
    with patch.object(my_submodule, "my_module_level_attribute", "some_other_value"): # Using string paths the patch arg would be  "myapp.mymodule.my_submodule.my_module_level_attribute"        
        actual = my_submodule.my_module_level_attribute
        expected = "some_other_value"
        
        assert actual == expected

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

patch_target-0.1.8.tar.gz (3.0 kB view details)

Uploaded Source

Built Distribution

patch_target-0.1.8-py3-none-any.whl (3.3 kB view details)

Uploaded Python 3

File details

Details for the file patch_target-0.1.8.tar.gz.

File metadata

  • Download URL: patch_target-0.1.8.tar.gz
  • Upload date:
  • Size: 3.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.6.1 CPython/3.10.13 Darwin/21.6.0

File hashes

Hashes for patch_target-0.1.8.tar.gz
Algorithm Hash digest
SHA256 b3e502f88bba0b55b02a124f5f61de408ec027050da99d22f873fa6dbe46ac9e
MD5 e13fc09a12e485c9db820727639c7f26
BLAKE2b-256 abedf19db8070e0014f61b80b9817a858e27de4c11bb9b8f898b752a17bd095d

See more details on using hashes here.

File details

Details for the file patch_target-0.1.8-py3-none-any.whl.

File metadata

  • Download URL: patch_target-0.1.8-py3-none-any.whl
  • Upload date:
  • Size: 3.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.6.1 CPython/3.10.13 Darwin/21.6.0

File hashes

Hashes for patch_target-0.1.8-py3-none-any.whl
Algorithm Hash digest
SHA256 2a54d01ef5f4da755d7077ef4ba0fcfd56de68186fbc53bb0853e64a653ffa78
MD5 ddf6f377f36524bfa379f6748df042e1
BLAKE2b-256 147034e5007fb0e39be76ae6f35daca03ba4e2c5a6a7cc645ec7b4432c5f485b

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page