Mocking
70 % Unit tests 20 % Integration tests 10 % UI tests
- Mock (MagicMock), Build-in as of Python3.3
- flexmock
- mox
- Mocker
- dingus
- fudge
- MiniMock
def get_next_person(user):
person = get_random_person()
while person in user['people_seen']:
person = get_random_person()
return person
Problem: Dependency on get_random_person
Solution: Mock all dependencies (get_random_person)
from mock import patch
@patch("application.get_random_person")
def test_new_person(get_random_person):
# arrange
user = {'people_seen': []}
expceted_person = 'Katie'
get_random_person.return_value = 'Katie'
# action
actual_persion = get_next_person(user)
# assert
assert_equals(actual_person, expected_person)
Problem: What if you want the method everytime something different each call?
@patch.object(Application, "get_random_person")
def test_experienced_user(mock_get_rand_person):
# arrange
app = Application()
user = {'people_seen': ['Sarah', 'Mary']}
expected_person = 'Katie'
mock_get_rand_person.side_effect = ['Mary', 'Sarah', 'Katie']
# action
actual_person = app.get_next_person(user)
# assert
assert_equals(actual_person, expected_person)
Verifying Parameters
def evaluate(person1, person2):
if person1 in person2['likes']:
send_email(person1)
send_email(person2)
elif person1 in person2['dislikes']:
let_down_gently(person1)
elif person1 not in person2['likes'] \
and person1 not in person2['dislikes']:
give_it_time(person1)
@patch("application.send_email")
@patch("application.let_down_gently")
@patch("application.give_it_time")
def test_person2_dislikes_person1(mock_send_email, mock_let_down, mock_give_it_time):
# arrange
person1 = 'Bill'
person2 = {
'likes': ['Sam', 'Joey'],
'dislikes': ['Bill']
}
# action
evaluate(person1, person2)
mock_let_down.assert_called_once_with(person1)
assert_equals(mock_give_it_time.call_count, 0)
assert_equals(mock_send_email.call_count, 0)
How to test this:
def get_json(filename):
try:
return json.loads(open(filename).read())
expect (IOError, ValueError):
return ""
@patch("__buildin__.open")
def test_get_valid_json(mock_open):
# arrange
filename = "does_not_exist.json"
mock_file = Mock()
mock_file.read.return_value = '{"foo": "bar"}'
mock_open.return_value = mock_file
# action
actual_result = get_json(filename)
# assert
assert_equals({u'foo': u'bar'}, actual_result)
Mock exceptions
@patch("__buildin__.open")
def test_get_json_ioerror(mock_open):
# arrange
filename = "does_not_exist.json"
mock_open.side_effect = IOError
# action
actual_result = get_json(filename)
# assert
assert_equals('', actual_result)
@patch("__buildin__.open")
@patch("json.loads")
def test_get_json_value_error(mock_open, mock_loads):
# arrange
filename = "does_not_exist.json"
mock_file = Mock()
mock_file.read.return_value = '{"foo": "bar"}'
mock_open.return_value = mock_file
mock_loads.side_effect = ValueError
# action
actual_result = get_json(filename)
# assert
assert_equals('', actual_result)
Mock global variable
my_pkg/adder.py
from filelock import Timeout, FileLock
lock = FileLock("/tmp/lock-file.lock")
def add(a, b):
with lock.acquire(timeout=10):
return a + b
my_pkg/test_adder.py
@mock.patch("my_pkg.adder.lock")
def test_it_should_add_two_numbers(mocked_lock):
my_pkg.add(1, 2)
mocked_lock.acquire.assert_called_once_with(timeout=10)
Mock function variable
my_pkg/adder.py
from filelock import Timeout, FileLock
def add(a, b):
lock = FileLock("/tmp/lock-file.lock")
with lock.acquire(timeout=10):
return a + b
my_pkg/test_adder.py
@mock.patch("my_pkg.adder.FileLock")
def test_it_should_add_two_numbers(mocked_filelock):
instance = mocked_filelock.return_value
my_pkg.add(1, 2)
instance.acquire.assert_called_once_with(timeout=10)