<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <meta content="text/html; charset=ISO-8859-1"
      http-equiv="Content-Type">
  </head>
  <body bgcolor="#ffffff" text="#000000">
    On 28/12/2010 15:32, Yoni Tsafir wrote:
    <blockquote
      cite="mid:AANLkTimZJ1aVkb5nFDPXyVAKvcRF+GF_wy=GvxXQ8oK=@mail.gmail.com"
      type="cite">
      <div dir="ltr">Hi guys,
        <div>I'm pretty new to the world of testing in python, and
          there's a problem I ran into while trying to mock a decorator
          in the code I'm testing.</div>
        <div>
          <meta http-equiv="content-type" content="text/html;
            charset=ISO-8859-1">
          Tell me what am I doing wrong or if this is a missing feature
          in the mock library.</div>
        <div><br>
        </div>
      </div>
    </blockquote>
    <br>
    The fundamental problem is that decorators are applied when the
    module is imported (when the function is *created*). Attempting to
    mock out the decorator later has no effect because the decorator has
    already been used.<br>
    <br>
    You could either reorganise your code to have catch_bad_args
    delegate to another function that you can mock or patch both
    sys.stderr and sys.exit and test the code by its effects.<br>
    <br>
    I would be tempted to have catch_bad_args call a _report_bad_args
    function with the exception instance. You could then just patch this
    function to test the behaviour.<br>
    <br>
    Something like:<br>
    <br>
    <br>
    def catch_bad_args(func):<br>
    &nbsp;&nbsp;&nbsp; def call(*args, **kwargs):<br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try:<br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return func(*args, **kwargs)<br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; except BadArgsError, e:<br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _report_bad_args(e)<br>
    <br>
    def _report_bad_args(e):<br>
    &nbsp;&nbsp;&nbsp; print &gt;&gt; sys.stderr, e.args[0]<br>
    &nbsp;&nbsp;&nbsp; print<br>
    &nbsp;&nbsp;&nbsp; sys.exit(1)<br>
    <br>
    In general having code call sys.exit directly makes it harder to
    test. If possible you should organise your application to have a
    single code path for exiting, with a single point that needs mocking
    / patching to override this behaviour.<br>
    <br>
    If your application entry point looks (something) like this:<br>
    <br>
    def main():<br>
    &nbsp;&nbsp;&nbsp; try:<br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; args = handle(args)<br>
    &nbsp;&nbsp;&nbsp; except BadArgsError as e:<br>
    &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; _handle_bad_args(e)<br>
    &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return 1<br>
    &nbsp;&nbsp;&nbsp; else:<br>
    &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; do_other_stuff()<br>
    <br>
    if __name__ == '__main__':<br>
    &nbsp;&nbsp;&nbsp; result = main()<br>
    &nbsp;&nbsp;&nbsp; sys.exit(result)<br>
    <br>
    Then you can test your main function handles bad arguments without
    worrying about it calling sys.exit().<br>
    <br>
    To test the return code with bad arguments you can use subprocess or
    similar as a complete "functional test" that executes the
    application in the same way as the user would.<br>
    <br>
    All the best,<br>
    <br>
    Michael<br>
    <blockquote
      cite="mid:AANLkTimZJ1aVkb5nFDPXyVAKvcRF+GF_wy=GvxXQ8oK=@mail.gmail.com"
      type="cite">
      <div dir="ltr">
        <div>I have the following code:</div>
        <div><br>
        </div>
        <div>a.py:</div>
        <div>-------</div>
        <div>
          <div>import sys</div>
          <div><br>
          </div>
          <div>class BadArgsError(Exception):</div>
          <div>&nbsp;&nbsp; &nbsp;pass</div>
          <div><br>
          </div>
          <div>
            <br>
          </div>
          <div>def catch_bad_args(func):</div>
          <div>&nbsp;&nbsp; &nbsp;def call(*args, **kwargs):</div>
          <div>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;try:</div>
          <div>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;return func(*args, **kwargs)</div>
          <div>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;except BadArgsError, e:</div>
          <div>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;print &gt;&gt; sys.stderr, e.args[0]</div>
          <div>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;print</div>
          <div>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;sys.exit(1)</div>
          <div><br>
          </div>
          <div>&nbsp;&nbsp; &nbsp;call.__name__ = func.__name__</div>
          <div>&nbsp;&nbsp; &nbsp;call.__dict__ = func.__dict__</div>
          <div>&nbsp;&nbsp; &nbsp;call.__doc__ &nbsp;= func.__doc__</div>
          <div>&nbsp;&nbsp; &nbsp;return call</div>
          <div style="text-decoration: underline;"><br>
          </div>
        </div>
        <div>b.py</div>
        <div>------</div>
        <div>
          <div>from a import BadArgsError, catch_bad_args</div>
          <div><br>
          </div>
          <div>@catch_bad_args</div>
          <div>def foo(arg):</div>
          <div>&nbsp;&nbsp; &nbsp;if arg &gt; 2:</div>
          <div>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;raise BadArgsError("arg is bigger than 2")</div>
          <div><br>
          </div>
          <div>&nbsp;&nbsp; &nbsp;print "arg is ok"</div>
        </div>
        <div><br>
        </div>
        <div>test_b.py</div>
        <div>-------------</div>
        <div>
          <div>import unittest</div>
          <div>from b import foo</div>
          <div>from a import BadArgsError</div>
          <div>from mock import patch</div>
          <div>&nbsp;&nbsp; &nbsp;</div>
          <div>def do_nothing_decorator(func):</div>
          <div>&nbsp;&nbsp; &nbsp;return func</div>
          <div>&nbsp;&nbsp; &nbsp;</div>
          <div>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;</div>
          <div>
            class BTest(unittest.TestCase):</div>
          <div>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;@patch("a.catch_bad_args", do_nothing_decorator)</div>
          <div>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;def test_three_raises(self):</div>
          <div>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;self.assertRaises(BadArgsError, foo, 3)</div>
          <div>
            <div><br>
            </div>
            <div><br>
            </div>
            <div>
              <meta http-equiv="content-type" content="text/html;
                charset=ISO-8859-1">
              <div>Now running the tests:</div>
            </div>
            <div>-------------------------------</div>
            <div>
              <div># python test_b.py</div>
              <div>arg is bigger than 2</div>
              <div>
                <br>
              </div>
              <div>E</div>
              <div>======================================================================</div>
              <div>ERROR: test_three_raises (__main__.BTest)</div>
              <div>----------------------------------------------------------------------</div>
              <div>Traceback (most recent call last):</div>
              <div>&nbsp;&nbsp;File "test_b.py", line 13, in test_three_raises</div>
              <div>&nbsp;&nbsp; &nbsp;self.assertRaises(BadArgsError, foo, 3)</div>
              <div>&nbsp;&nbsp;File "/usr/lib/python2.6/unittest.py", line 336, in
                failUnlessRaises</div>
              <div>&nbsp;&nbsp; &nbsp;callableObj(*args, **kwargs)</div>
              <div>&nbsp;&nbsp;File "/tmp/a.py", line 14, in call</div>
              <div>&nbsp;&nbsp; &nbsp;sys.exit(1)</div>
              <div>SystemExit: 1</div>
              <div><br>
              </div>
              <div>----------------------------------------------------------------------</div>
              <div>Ran 1 test in 0.014s</div>
              <div><br>
              </div>
              <div>FAILED (errors=1)</div>
            </div>
          </div>
        </div>
        <div><br>
        </div>
        <div><br>
        </div>
        <div>What am I doing wrong? I tried to change the patch to
          "b.catch_bad_args" and I get the same results, I also tried
          @patch.object and other combinations... No help</div>
        <div><br>
        </div>
        <div><br>
        </div>
        <div>Thanks a lot!</div>
      </div>
      <pre wrap="">
<fieldset class="mimeAttachmentHeader"></fieldset>
_______________________________________________
testing-in-python mailing list
<a class="moz-txt-link-abbreviated" href="mailto:testing-in-python@lists.idyll.org">testing-in-python@lists.idyll.org</a>
<a class="moz-txt-link-freetext" href="http://lists.idyll.org/listinfo/testing-in-python">http://lists.idyll.org/listinfo/testing-in-python</a>
</pre>
    </blockquote>
    <br>
    <br>
    <pre class="moz-signature" cols="72">-- 
<a class="moz-txt-link-freetext" href="http://www.voidspace.org.uk/">http://www.voidspace.org.uk/</a>

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 blessing <a class="moz-txt-link-freetext" href="http://www.sqlite.org/different.html">http://www.sqlite.org/different.html</a>
</pre>
  </body>
</html>