If you are only intending to test the exception as-is, mocking the secret_fetcher
argument is basically inconsequential at this stage, as a simple None
value will do as it will never be touched, but here's an example to kick things off:
# include the `get_param` function by import or inline here
import unittest
from unittest.mock import Mock
class TestApp(unittest.TestCase):
def test_get_params_missing_url(self):
env = {'missing': 'fail'}
secret_fetcher = Mock()
with self.assertRaises(KeyError):
get_params(env, secret_fetcher)
(Do note that I prefer using assertRaises
as a context manager to ensure a more natural way of writing the calling of a function; do note that the first exception in the with
block will prevent subsequent code from being executed in that block, so it's recommended that only one logical expression be in the assertRaises
context manager, or at the very least be the last line; i.e. this can only test one exception at a time)
Running this one test:
$ python -m unittest demo.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
However, given that the theory behind using mocks in the context of unit testing is to enable the testing of the code using the bare minimum external dependencies (i.e. using no other real modules, methods or classes; this keep the testing done against just the relevant unit), using the other features provided by unittest.mock.Mock
and friends may simplify this goal.
You may wish to ensure that get_secret
was called with the correct argument and the expected result was to be returned, should the correct env
was provided. Also testing that the error handling was dealt with as expected. Additional methods that may be appended to the TestApp
class above:
def test_get_params_success(self):
env = {'API_URL': 'https://api.example.com'}
def get_secret(arg):
return arg
secret_fetcher = Mock()
secret_fetcher.get_secret.side_effect = get_secret
url, key, secret = get_params(env, secret_fetcher)
self.assertEqual(url, 'https://api.example.com')
self.assertEqual(key, 'CLIENT-KEY')
self.assertEqual(secret, 'CLIENT-SECRET')
# Test that the secret_fetcher.get_secret helper was called
# with both arguments
secret_fetcher.get_secret.assert_any_call('CLIENT-KEY')
secret_fetcher.get_secret.assert_any_call('CLIENT-SECRET')
self.assertEqual(
secret_fetcher.get_secret.call_args[0], ('CLIENT-SECRET',))
def test_get_params_failure(self):
env = {'API_URL': 'https://api.example.com'}
secret_fetcher = Mock()
secret_fetcher.get_secret.side_effect = ValueError('wrong value')
with self.assertRaises(ValueError):
get_params(env, secret_fetcher)
# Test secret_fetcher.get_secret helper was only called with
# the first CLIENT-KEY argument
# Python 3.8 can check secret_fetcher.get_secret.call_args.args
self.assertEqual(
secret_fetcher.get_secret.call_args[0], ('CLIENT-KEY',))
Testing it out:
$ python -m unittest demo.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
Note that while I have absolute zero information about what your SecretFetcher
class does or has, the three test cases together tested the provided get_params
function to ensure that it behaves as expected, including testing how it should have used the secret_fetcher.get_secret
, that it handles the error as expected, and that with all the provided test cases tested every line of code in the get_params example provided in the question.
Hopefully this served as a comprehensive example on how mocks might be used to satisfy the goals of unit testing.