[TIP] Dilemma with using mock objects

Tom Davis tom at recursivedream.com
Mon Apr 23 19:47:01 PDT 2012

On Mon, 23 Apr 2012 18:24:49 -0400, Tu Ted wrote:
> Please bear with me. Suppose I want to assert that result is in a
> particular format, I might set the return value to something as (True,
> 'Success')
> Sample code: http://pastebin.com/VraxaKjL
> def batch_move(self, *args, **kwargs):
>   '''
>   Move a batch of files to its respective destinations.
>   Return type: tuple (boolean, message)
>                       T/F    , 'string' / str(exception)
>   '''
>   srcs= kwargs.get('srcs', None)
>   dests = kwargs.get('dests', None)
>   try:
>     if srcs and dests:
>        # map srcs and dests into dictionary (srcs --> keys, dests -->
> values)
>        src_dest= dict(zip(srcs, dests))
>        for src, dest in src_dest:
>          if os.path.exists(src):
>            if os.path.exists(dest):
>               shutil.rmtree(dest)
>            shutil.move(src, dest)   # either way we will proceed to move
>          else:
>            return (False, '%s does not exist!' % src)
>        return (True, 'Success!')
>     else:
>        return (False, 'Something gone wrong with those kwargs...')
>   except Exception as e:
>     return (False, e)

I think you probably need src_dest.iteritems(), for starters...

> In order to get to return (True, 'Success!'), I have
> 1. patch os.path.exists with True as return value. But in one unittest I
> want to skip this, how do I patch that os.path.exists?
>         if os.path.exists(dest):  # I want to skip this
>              shutil.rmtree(dest)

If by "skip" you mean go to the "else" block, you could just configure the mock
to return False instead. If by "skip" you mean you want the actual function to
be called, just don't mock that test (if using a decorator) or stop the patch
(if patching manually).

> 2. How do I patch shutil.move(src, dest)? Do I just give True so it doesn't
> generate error? What if I want the case it fails and caught an exception?
> How do I simulate that? (I wouldn't always know which exception to catch,
> the primarily reason to use Exception as e).

You should test every case, both success and failure. You can consult the
documentation or the source for `shutil.move` in order to write tests for each
failure case. In the case that neither of these are completely helpful, just try
different scenarios and see what happens. Why would moves fail? Destination
exists, insufficient permissions, etc.

> 3. If I actually pass the function, does it really mean no exception caught
> and it went through every single line? Or is it because I set
> `mock_object.return_value = (True, 'Success!')?

A library like coverage.py will tell you that more easily, but if all you did
was mock exists() and move(), then you could get every other line to run
(presuming you had True/False cases that ultimately cover every code path).

Now, there are a number of ways you're making your life more difficult by the
way this function is written. First, it has to validate its arguments manually;
that adds another code path (bad kwargs). Second, it couples the success of the
function with the presentation of its failure; this could lead you to test
oft-altered string values (which probably have no place in this function to
begin with). Finally, instead of using the exception system you are
bulk-catching exceptions and returning error indicators instead. As you
mentioned above, this gives you the annoying requirement of figuring out every
possible exception instead of leaving it to the caller to properly handle them.

> 4. I am only using two dependencies here, do I need to patch out all the
> external dependencies such as (os, sys, math, datetime) all in one? Or if
> my function is using other functions (which are refactored)
>  def f1(*args, **kwargs):
>     f2(..)   # use math, plot, datetime
>     f3(..)   # use math and datetime
>     f4(..)   # use datetime

You only need to patch that which you do not want to run as normal. In the
example you gave, you should technically mock calls to each of f2(), f3(), and
f4(), simply asserting on the values passed to them. This, of course, presumes
you have written tests for each of these functions elsewhere, thus do not need
to test them indirectly a second time.

> Thanks. Sorry for the long questions. I really want to be good at writing
> unittests.

An excellent goal to those on this list, I am sure ;)

Hopefully my answers were somewhat useful...

> --
> Ted

> _______________________________________________
> testing-in-python mailing list
> testing-in-python at lists.idyll.org
> http://lists.idyll.org/listinfo/testing-in-python

More information about the testing-in-python mailing list