Skip to main content

Mega mocking capabilities - stop using dot-notated paths!

Project description

MegaMock

Pew pew! Patch objects, variables, attributes, etc by passing in the thing in question, rather than passing in dot-delimited paths! Also sane defaults for mocking behavior!

Supported Python Versions: 3.10+

Why Use MegaMock?

MegaMock was created to address some shortcomings in the built-in Python library:

  • Legacy code holds back "best practice" defaults, so many developers write sub-optimal mocks that allow things that should not be allowed. Likewise, writing better mocks are more work, so there's a tendency to write simpler code because, at that point in time, the developer felt that is all that was needed. MegaMock's simple interface provides sane defaults.
  • mock.patch is very commonly used, and can work well when autospec=True, but has the drawback that you need to pass in a string to the thing that is being patched. Most (all?) IDEs do not properly recognize these strings as references to the objects that are being patched, and thus automated refactoring and reference finding skips them. Likewise, automatically getting a dot referenced path to an object is also commonly missing functionality. This all adds additional burden to the developer. With MegaPatch, you can import an object as you normally would into the test, then pass in thing itself you want to patch. This even works for methods, attributes, and nested classes!
  • mock.patch has a gotcha where the string you provide must match where the reference lives. So, for example, if you have in my_module.py: from other_module import Thing, then doing mock.patch("other_module.Thing") won't actually work, because the reference in my_module still points to the original. You can work around this by doing import other_module and referencing Thing by other_module.Thing. MegaMock does not have this problem, and it doesn't matter how you import.

Example Usage

Production Code

from module.submodule import MyClassToMock


def my_method(...):
    ...
    a_thing = MyClassToMock(...)
    do_something_with_a_thing(a_thing)
    ...


def do_something_with_a_thing(a_thing: MyClassToMock) -> None:
    result = a_thing.some_method(...)
    if result == "a value":
        ...

Test Code

from megamock import MegaPatch
from module.submodule import MyClassToMock


def test_something(...):
    patched = MegaPatch.it(MyClassToMock.some_method)
    patched.return_value = "a value"

    my_method(...)

Documentation

Installation

pip install megamock

Usage

Import and execution order is important for MegaMock. When running tests, you will need to execute the start_import_mod function prior to importing any production or test code. You will also want it so the loader is not used in production.

With pytest, this is easily done by using the included pytest plugin. You can use it by adding -p megamock.plugins.pytest to the command line.

Command line example:

pytest -p megamock.plugins.pytest

pyproject.toml example:

[tool.pytest.ini_options]
addopts = "-p megamock.plugins.pytest"

The pytest plugin also automatically stops MegaPatches after each test. To disable this behavior, pass in the --do_not_autostop_megapatches command line argument.

In tests, the MegaMock class replaces the mock classes MagicMock and Mock. MegaPatch.it(...) replaces patch(...). Currently, there is no substitute for patch.object although MegaPatch.it should work on global instances (singletons).

from megamock import MegaMock

def test_something(...):
    manager = MegaMock(MyManagerClass)
    service = SomeService(manager)
    ...
from elsewhere import Service

from megamock import MegaPatch

def test_something(...):
    patched = MegaPatch.it(Service.make_external_call)
    patched.return_value = SomeValue(...)
    service = SomeService(...)

    code_under_test(service)
    ...

You can focus your production code on creating an intuitive, "batteries included" interface for developers without making compromises for testability. Please see the guidance section (TODO) for more information on how and when you would use MegaMock.

Use Case Examples

All use cases below have the following import:

from megamock import MegaMock, MegaPatch

Creating a mock instance of a class:

from my_module import MyClass

mock_instance = MegaMock(MyClass)

Creating a mock class itself:

from my_module import MyClass

mock_class = MegaMock(MyClass, instance=False)

Patching a class:

from my_module import MyClass

mock_patch = MegaPatch(MyClass)

# the class itself
mock_patch.new_value

# the class instance
mock_patch.return_value

# the return value of the __call__ method on the class
mock_patch.return_value.return_value

Patching a class attribute:

from my_module import MyClass

# temporarily update the hard coded default max retries to 0
mega_patch = MegaPatch(MyClass.max_retries, new=0)

Patching a class method:

from my_module import MyClass

mega_patch = MegaPatch.it(MyClass.my_method, return_value=...)

Alternatively:

mega_patch = MegaPatch.it(MyClass.my_method)
mega_patch.mock.return_value = ...
mega_patch = MegaPatch.it(MyClass.my_method)
mega_patch.new_value.return_value = ...

You can also alter the return value of your mock without creating a separate mock object first.

mega_patch.return_value.user = SomeUser()

Working with MegaPatch and classes:

mega_patch.new_value is the class type itself

mega_patch = MegaPatch.it(MyClass)

mega_patch.new_value.x is MyClass.x

mega_patch.return_value is the class instance returned

mega_patch = MegaPatch.it(MyClass)

mega_patch.return_value is MyClass()

Patching a module attribute:

import my_module

MegaPatch.it(my_module.some_attribute, new=...)

Patching a method of a nested class:

import my_module

MegaPatch.it(
    my_module.MyClass.MyNestedClass.some_method,
    return_value=...
)

Behavior differences from mock

  • Using MegaMock is like using the mock.create_autospec() function
  • Using MegaPatch is like setting autospec=True
  • Mocking a class by default returns an instance of the class instead of a mocked type. This is like setting instance=True

Art Gallery

MegaMock

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

megamock-0.1.0a4.tar.gz (11.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

megamock-0.1.0a4-py3-none-any.whl (10.7 kB view details)

Uploaded Python 3

File details

Details for the file megamock-0.1.0a4.tar.gz.

File metadata

  • Download URL: megamock-0.1.0a4.tar.gz
  • Upload date:
  • Size: 11.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.3.2 CPython/3.11.1 Linux/5.10.16.3-microsoft-standard-WSL2

File hashes

Hashes for megamock-0.1.0a4.tar.gz
Algorithm Hash digest
SHA256 16eea3e2437cce3816da94faaff87220e8fcb1ebfd74d9ca44479d0cdea0afb2
MD5 bada4278fb2c082850944e56d9cd0940
BLAKE2b-256 3019f9bf2561a1e539259d5b83296bf85efcc96465d18be67a72e0c72a3f605f

See more details on using hashes here.

File details

Details for the file megamock-0.1.0a4-py3-none-any.whl.

File metadata

  • Download URL: megamock-0.1.0a4-py3-none-any.whl
  • Upload date:
  • Size: 10.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.3.2 CPython/3.11.1 Linux/5.10.16.3-microsoft-standard-WSL2

File hashes

Hashes for megamock-0.1.0a4-py3-none-any.whl
Algorithm Hash digest
SHA256 075115ea8c20e51425d24fd5b5f5308dac5dc07d3c84aa53bca1d6c371eaf115
MD5 6f1305e49889bdd9dd507d6b5020e19f
BLAKE2b-256 1797f23fa534b59ffa655bd8d48ca17138d5a627d15d003a6994c87df2b9529a

See more details on using hashes here.

Supported by

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