unittest.mock: asserting partial match for method argument

Solution 1:

import mock

class AnyStringWith(str):
    def __eq__(self, other):
        return self in other

...
result = database.Query('complicated sql with an id: %s' % id)
database.Query.assert_called_once_with(AnyStringWith(id))
...

Preemptively requires a matching string

def arg_should_contain(x):
    def wrapper(arg):
        assert str(x) in arg, "'%s' does not contain '%s'" % (arg, x)
    return wrapper

...
database.Query = arg_should_contain(id)
result = database.Query('complicated sql with an id: %s' % id)

UPDATE

Using libraries like callee, you don't need to implement AnyStringWith.

from callee import Contains

database.Query.assert_called_once_with(Contains(id))

https://callee.readthedocs.io/en/latest/reference/operators.html#callee.operators.Contains

Solution 2:

You can just use unittest.mock.ANY :)

from unittest.mock import Mock, ANY

def foo(some_string):
    print(some_string)

foo = Mock()
foo("bla")
foo.assert_called_with(ANY)

As described here - https://docs.python.org/3/library/unittest.mock.html#any

Solution 3:

You can use match_equality from PyHamcrest library to wrap the matches_regexp matcher from the same library:

from hamcrest.library.integration import match_equality

with patch(database) as MockDatabase:
  instance = MockDatabase.return_value
  ...
  expected_arg = matches_regexp(id)
  instance.Query.assert_called_once_with(match_equality(expected_arg))

This method is mentioned also in Python's unittest.mock documentation:

As of version 1.5, the Python testing library PyHamcrest provides similar functionality, that may be useful here, in the form of its equality matcher (hamcrest.library.integration.match_equality).

If you don't want to use PyHamcrest, the documentation linked above also shows how to write a custom matcher by defining a class with an __eq__ method (as suggested in falsetrus answer):

class Matcher:
    def __init__(self, compare, expected):
        self.compare = compare
        self.expected = expected

    def __eq__(self, actual):
        return self.compare(self.expected, actual)

match_foo = Matcher(compare, Foo(1, 2))
mock.assert_called_with(match_foo)

You could replace the call to self.compare here with your own regex matching and return False if none found or raise an AssertionError with a descriptive error message of your choice.