4

I am writing tests in my app that will test whether a method was called. This is running in Python 3.4.3 and pytest-2.9.2. I am new to PyTest but very familiar with RSpec and Jasmine. I'm not sure how to setup the test so that it will test that imaplib.IMAP4_SSL is called.

My app structure:

/myApp
  __init__.py
  /shared
    __init__.py
    email_handler.py
  /tests
    __init__.py
    test_email_handler.py

email_handler.py

import imaplib
def email_conn(host):
    mail = imaplib.IMAP4_SSL(host)  
    return mail;

What I have so far for my test: test_email_handler.py

import sys   
sys.path.append('.')  

from shared import email_handler 

def test_email_handler():   
     email_handler.email_conn.imaplib.IMAP4_SSL.assert_called_once 

This obviously fails. How can I setup this test so that it tests if imaplib.IMAP4_SSL is called? Or is there a better way to setup the test suite in my app so this will support testing more effectively?

analyticsPierce
  • 2,979
  • 9
  • 57
  • 81
  • You might want to have a look at [pytest-mock](https://github.com/pytest-dev/pytest-mock/). – das-g Jul 13 '16 at 22:14
  • @das-g yes, that is interesting. how to apply for this use case though? I am thinking this is more like a spy than a mock. Can I leverage this library to fix this problem? – analyticsPierce Jul 14 '16 at 17:16
  • Despite their name, the stand-in objects pytest-mock (and [`unittest.mock`](https://docs.python.org/3/library/unittest.mock.html) and the [`mock`](https://pypi.python.org/pypi/mock) package) provide are what [xUnit Patterns.com calls "Test Spy"](http://xunitpatterns.com/Mocks,%20Fakes,%20Stubs%20and%20Dummies.html): They capture calls to them ("indirect outputs of the system under test") for later verification. (What xUnit Patterns.com calls "Mock Object" would work for your case, too, though. See also [Mocks Aren't Stubs](http://martinfowler.com/articles/mocksArentStubs.html).) – das-g Jul 15 '16 at 07:43

3 Answers3

3

Here's an example using unittest.mock from the Python 3.5.2 standard library:

test_email_handler.py

import sys
from unittest import mock
sys.path.append('.')

from shared import email_handler

@mock.patch.object(email_handler.imaplib, 'IMAP4_SSL')
def test_email_handler(mock_IMAP4_SSL):
    host = 'somefakehost'
    email_handler.email_conn(host)
    mock_IMAP4_SSL.assert_called_once_with(host)

Note the @mock.patch.object decorator that replaces IMAP4_SSL with a mock object, which is added as an argument. Mock is a powerful tool for testing that can be quite confusing for new users. I recommend the following for further reading:

https://www.toptal.com/python/an-introduction-to-mocking-in-python

http://engineroom.trackmaven.com/blog/mocking-mistakes/

http://alexmarandon.com/articles/python_mock_gotchas/

Lex Scarisbrick
  • 1,540
  • 1
  • 24
  • 31
0

what you can do is this :

email_handler.py

import imaplib

def email_conn(host):
    print("We are in email_conn()")
    mail = imaplib.IMAP4_SSL(host)
    print(mail)
    return mail;

test_email_handler.py

import sys   
sys.path.append('.')  

from shared import email_handler 

def test_email_handler():
    print("We are in test_email_handler()")
    email_handler.email_conn.imaplib.IMAP4_SSL.assert_called_once
    print(email_handler.email_conn.imaplib.IMAP4_SSL.assert_called_once) # this will give you the result of the function (in theory) 

Basically, what you do is printing what the functions returns. If there is no error, the function should have been executed.

What you could also do, is modifying the source code of imaplib in order to put a print inside the function you're calling.

Good luck !

Marc Schmitt
  • 1,299
  • 3
  • 10
  • 7
0

This sounds like a question of code coverage: was this line executed?

For python coverage tool is: https://coverage.readthedocs.io

Pytest built plugin based on that tool, which is very convenient: https://pypi.python.org/pypi/pytest-cov

Dmitry Tokarev
  • 1,851
  • 15
  • 29
  • I do want to include more code coverage information. However, the test is failing because the assertion in the test_email_handler is not setup correctly. I get an error on the `assert_called_once` line. – analyticsPierce Jul 20 '16 at 06:37
  • @analyticsPierce see if answer to this question will help: http://stackoverflow.com/questions/3829742/assert-that-a-method-was-called-in-a-python-unit-test. Also there is https://github.com/pytest-dev/pytest-mock that may provide need functionality – Dmitry Tokarev Jul 20 '16 at 07:32
  • 1
    I have seen that question but I think it is unittest instead of py.test. I have also used similar assert conditions but they all fail. That is why I added a bounty so I could get a clear code example. Yes, I am aware of pytest-mock. My question there is how to leverage it to solve this problem. – analyticsPierce Jul 20 '16 at 15:53