pythonunit-testingmocking

unittest.mock: asserting partial match for method argument


Rubyist writing Python here. I've got some code that looks kinda like this:

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

database.Query is mocked out, and I want to test that the ID gets injected in correctly without hardcoding the entire SQL statement into my test. In Ruby/RR, I would have done this:

mock(database).query(/#{id}/)

But I can't see a way to set up a 'selective mock' like that in unittest.mock, at least without some hairy side_effect logic. So I tried using the regexp in the assertion instead:

with patch(database) as MockDatabase:
  instance = MockDatabase.return_value
  ...
  instance.Query.assert_called_once_with(re.compile("%s" % id))

But that doesn't work either. This approach does work, but it's ugly:

with patch(database) as MockDatabase:
  instance = MockDatabase.return_value
  ...
  self.assertIn(id, instance.Query.call_args[0][0])

Better ideas?


Solution

  • 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