Closed ghsatpute closed 2 years ago
Both probably. The example assumes you also import from mockito import any
with the downside of overwriting the builtin any
function.
You can instead also import one of the aliases from mockito import any_, ANY
whichever you find easier to read. Or import under a different name from mockito import any as any_
if that would matter.
The docs are a bit sparse here. Some people completely hate the idea of reusing builtin names, other people are okay with it.
Okay. Thanks for the information. I changed the any(...)
to ANY(...)
in following code.
argument_captor = captor(ANY(str))
region_captor = captor(ANY(str))
when(boto3).client(argument_captor, region_captor).thenReturn(
self.ses_client_mock)
The code I want to test has below line
ses_client = boto3.client('ses', region_name='us-west-2')
Here, the first captor captures proper value but the second one doesn't. It gets None value. What might be the reason?
This generally works for me, in that I don't see the None
value. On the other hand, the exact code snippet would not work for me.
Because you mock using .client(a, b).thenReturn(...)
. If I mock using positional arguments, I actually get an error if I then call with keyword arguments (a, b='hello')
. You might have actually different code running.
Now, captor
is usually not needed anyway. Why don't you just use
when(boto3).client('ses', region_name='us-west-2').thenReturn(someMock)
? Just using this snippet you can be sure client
gets called with exact these arguments because it will throw otherwise. (Default mode is strict!) Basically without further ado, with just one when
call you kinda expect or only allow these arguments.
Actually, I gave one simplified example and in that example I can get away with the way you mentioned but in the following example, it wouldn't be possible. Because lots of pre-processing.
def send_email(recipient, subject, template):
...
ses_client.send_email(
recipient={
"ToAddresses": [
]
},
content={
"HTML": {
"Body": {
},
"Text": {
}
}
},
# Few other parameters
)
(This is not the actual code, I've just written it out of my head)
As you can see, I have to write lots of preprocessing code to generate the output for my when
statement matchers.
Rather it would be just simple to use captor and then validate the necessary parts.
Can you write down a reduced testcase that shows that captor.value
is None although you used it?
I can only think of that you're actually stubbing (when
ing) a mock()
which is by default not in strict mode. Then you call it with something unexpected, it will not throw, and the the captured value will be None
because it didn't match.
E.g.
> client = mock()
> a = captor(any_(str))
> when(client).send_email(a).thenReturn("Foo")
> client.send_email(1) # Note an int!
> repr(a.value)
'None'
> client.send_email("Hi")
'Foo'
> a.value
'Hi'
Maybe just use mock(strict=True)
to see if it receives unexpected "interactions".
Just because you have a lot of arguments doesn't mean you should use captor
imo. In above example you could still do
when(client).send_email(
recipient=expected_recipient,
content=expected_content,
other_param=ANY,
...
).thenReturn(None)
The ...
literally denoting ignore the rest of the arguments.
Or you allow everything when(client).send_email(...)
in a setup and then explicitly verify(client).send_email("this", "that", ...)
in each test.
I am, indeed, using strict=true
.
After I changed the when
condition from
when(boto3).client(service_arg_captor, region_captor).thenReturn(mock_response)
to
when(boto3).client(service_arg_captor, region_name=region_captor).thenReturn(mock_response)
this worked. As I had used the named parameter in the call so I used the same in when
statement. My code to test was as below
ses_client = boto3.client('ses', region_name='us-west-2')
The example, where I couldn't possibly use when
and I had to use captor, even there, the problem was named parameters
Another scenario where the captor1.value
could be null, is when you define the wrong data type. For example, instead of str
you created captor with dict
One more scenario, where captor value is wrong, when the unwanted parameter is introduced in between. For example, a=1, b=2
, if a
is unwanted b's captor will get None
value
It would really help people if this gets documented. I can contribute if needed.
We're always taking PR's. ❤️
But if a strict
mock doesn't throw on unwanted/unexpected invocations that would be a bug, so I would like to replicate and fix that.
I could not reproduce using
class TestBoto:
def test_a(self, unstub):
from . import module
from mockito import captor, any
service_captor = captor(any(str))
region_captor = captor(any(str))
when(module).client(service_captor, region_captor).thenReturn("mocked")
assert "mocked" == module.client('ses', region_name='us-west-2')
where module.client
is just
def client(*args, **kwargs):
return "Not patched"
(which is probably the same as https://github.com/boto/boto3/blob/dc5a29a372d7d2198dd8b783721bc29acde1eef9/boto3/__init__.py#L85-L91 ) because that gives me:
But another pitfall could be that your code under test actually catches all exceptions. In this case some captors that matched have a value while the non-matching captors still have a None
. (In the screenshot you can see that service_captor
has received 'ses'
.)
Beside the ugly repr
for the captors and matchers, partially applying is probably confusing here.
Closing as stranded. Note that I reimplemented captor
and the confusing partial applying has been fixed.
I'm trying out
captor
example given hereThis is my code
This throws an exception when I ran it
Am I missing something or the example is not up to date?