Lars Grundei
Lars Grundei

Reputation: 111

Python 3: how to tests exceptions within with?

I have problems to test exceptions which would be raised within a with in python 3.4. I just can't get the tests run for this peace of code:

import logging
...
class Foo(object):
    ...
    def foo(self, src, dst):
        try:
            with pysftp.Connection(self._host, username=self._username, password=self._password) as connection:
                connection.put(src, dst)
                connection.close()
        except (
                ConnectionException,
                CredentialException,
                SSHException,
                AuthenticationException,
                HostKeysException,
                PasswordRequiredException
        ) as e:
            self._log.error(e)

And this is how I want to test it:

import logging
...
class TestFoo(TestCase):
    @parameterized.expand([
        ('ConnectionException', ConnectionException),
        ('CredentialException', CredentialException),
        ('SSHException', SSHException),
        ('AuthenticationException', AuthenticationException),
        ('HostKeysException', HostKeysException),
        ('PasswordRequiredException', PasswordRequiredException),
    ])
    @patch('pysftp.Connection', spec_set=pysftp.Connection)
    def test_foo_exceptions(self, _, ex, sftp_mock):
        """
        NOTE: take a look at:
              http://stackoverflow.com/questions/37014904/mocking-python-class-in-unit-test-and-verifying-an-instance
              to get an understanding of __enter__ and __exit__
        """
        sftp_mock.return_value = Mock(
            spec=pysftp.Connection,
            side_effect=ex,
            __enter__ = lambda self: self,
            __exit__ = lambda *args: None
        )
        foo = Foo('host', 'user', 'pass', Mock(spec_set=logging.Logger))
        foo.foo('src', 'dst')
        self.assertEqual(foo._log.error.call_count, 1)

But it fails - output:

Failure
...
AssertionError: 0 != 1

Upvotes: 1

Views: 488

Answers (1)

Martijn Pieters
Martijn Pieters

Reputation: 1121824

Your sftp_mock.return_value object is never called, so the side_effect is never triggered and no exception is raised. It would only be called if the return value of pysftp.Connection(...) was itself called again.

Set the side effect directly on the mock:

sftp_mock.side_effect = ex

Note that now the pysftp.Connection(...) expression raises the exception and it no longer matters that the return value of that expression would have been used as a context manager in a with statement.

Note that your exceptions will complain about not getting any arguments; pass in instances of your exceptions, not the type:

@parameterized.expand([
    ('ConnectionException', ConnectionException('host', 1234)),
    # ... etc.
])

Upvotes: 1

Related Questions