[TIP] Issue with "mock" package and isinstance()
Tom Davis
tom at recursivedream.com
Fri Jul 29 10:37:48 PDT 2011
On 07/29/2011 01:29 PM, Michael Foord wrote:
> On 29/07/2011 18:03, Tom Davis wrote:
>> On 07/29/2011 12:42 PM, Michael Foord wrote:
>>> On 29/07/2011 17:31, Tom Davis wrote:
>>>> Hey all,
>>>>
>>>> I've been using Michael Foord's awesome /mock/ package and I've
>>>> loved it... now I'm running into an issue. I'm patching a class,
>>>> pretty simple:
>>>>
>>>> from my.module.path import Class
>>>> patch = mock.patch('my.module.path.Class', spec=Class)
>>>> mocked = patch.start()
>>>> mocked.return_value = MyReplacementClass()
>>>>
>>>> I'm doing things this way because I need to mock out a class, but
>>>> provide an alternate implementation that uses similar logic to the
>>>> real class (basically mocking an external API by turning it into a
>>>> memory-mapped API).
>>>>
>>>> Somewhere else, my code gets this mock client and a function calls:
>>>>
>>>> isinstance(client, my.module.path.Class): ...
>>>>
>>>> This call fails with:
>>>>
>>>> TypeError: isinstance() arg 2 must be a class, type, or tuple
>>>> of classes and type
>>>>
>>>> This was confusing to me, so I did a bit of digging to figure out
>>>> what /Class/ really was, but all was well. Here's what I got:
>>>>
>>>> <MagicMock spec='Class' id='50389456'>
>>>>
>>>
>>> Well yes - what you have here is a mock *instance* - it isn't a
>>> class it's a mock of a class. So you can't use this as the second
>>> argument to isinstance. This is one of the problems with type
>>> checking (not that it's always wrong - just that it causes this kind
>>> of difficulty).
>>>
>>> Actually it *would* be possible to make a mock object behave like a
>>> class, by providing __bases__ = (type,) on mock instances (or
>>> whatever the appropriate bases would be).
>> First off, thanks for the super fast reply! I'm trying to wrap up
>> this project and the intricacies of the final patching stage are
>> confusing me a bit. So if I understand this correctly, you're saying
>> that isinstance() is actually calling (or otherwise obtaining what i
>> set as "return_value") the mock?
>
> Not quite, no.
>
> In your test you are replacing "my.module.path.Class" with a mock. So
> when your code tries to use "my.module.path.Class" as the second
> argument to isinstance, it finds the mock. This is what your patching
> has done. You are telling patch to replace Class with a mock - so that
> is what has happened! Unless I have misunderstood - but that's what it
> looks like from both your code and description...
>
Quite right! However--and maybe this is where I am confused--I was under
the impression that providing the "spec" or "spec_set" argument to Mock
(or in this case, indirectly to Mock via patch()) allowed it to pass
isinstance() tests.
What I *really* want is for calling Class() to return a pre-built
replacement. I don't want Class itself to be an instance of anything, if
I can help it. I want it to be a class that "looks" just like the
original, except for the fact that it returns (e.g. via __new__) a
custom subclass. I thought patching it and providing "return_value" on
the patched result would do this. But I guess I am creating Mock
instances instead? Maybe my "mocked" object just needs to be a sublass
of Class with __new__ replaced? Perhaps via the "new" argument to
patch()? I will continue to investigate.
> Michael
>
>> The result would make sense, then, though I thought it just checked
>> its type? This is a bit above my meta-Python experience.
>>>
>>> Can you add this as a feature request:
>>>
>>> https://code.google.com/p/mock/issues/list
>>>
>> Provided I actually understand what it is (see above), I certainly will.
>>
>> Thanks again!
>>
>> -Tom
>>> All the best,
>>>
>>> Michael Foord
>>>>
>>>> Seems right to me! Additionally, if I change the instance check in
>>>> the production code to:
>>>>
>>>> isinstance(client, my.module.path.Class._spec_class): ...
>>>>
>>>> It works just fine! I can't do this for obvious reasons, but it
>>>> seems to prove that the MagicMock at least has the correct
>>>> information somewhere, it's just not being properly inspected by
>>>> isinstance().
>>>>
>>>> Any thoughts? I really need this to work.
>>>>
>>>>
>>>> _______________________________________________
>>>> testing-in-python mailing list
>>>> testing-in-python at lists.idyll.org
>>>> http://lists.idyll.org/listinfo/testing-in-python
>>>
>>>
>>> --
>>> http://www.voidspace.org.uk/
>>>
>>> May you do good and not evil
>>> May you find forgiveness for yourself and forgive others
>>> May you share freely, never taking more than you give.
>>> -- the sqlite blessinghttp://www.sqlite.org/different.html
>>
>
>
> --
> http://www.voidspace.org.uk/
>
> May you do good and not evil
> May you find forgiveness for yourself and forgive others
> May you share freely, never taking more than you give.
> -- the sqlite blessinghttp://www.sqlite.org/different.html
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.idyll.org/pipermail/testing-in-python/attachments/20110729/7f4c3902/attachment.htm>
More information about the testing-in-python
mailing list