Reputation: 2887
How do I use pytest to check that two different files in the same function are opened correctly?
open_two_files.py
def open_two_files(input_filepath, output_filepath):
open_output = open(output_filepath, 'w')
with open(input_filepath, 'r') as open_input:
for line in open_input:
open_output.write('foo')
I used this answer to build a test
test_open_two_files.py
import pytest
from unittest.mock import patch, mock_open
from open_two_files import *
def test_open_two_files():
open_mock = mock_open()
with patch('open_two_files.open', open_mock, create=True):
open_two_files("input_filepath", "output_filepath")
open_mock.assert_called_with("input_filepath", 'r')
# pytest passes with the following line commented out
open_mock.assert_called_with("output_filepath", 'w')
The error I get is
E AssertionError: expected call not found.
E Expected: open('output_filepath', 'w')
E Actual: open('input_filepath', 'r')
If I comment out the last line, the tests pass. The testing seems to only look at the last time that a file is opened. How do I test all occurrences of a file being opened please?
Upvotes: 1
Views: 520
Reputation: 2474
I created an open source pytest fixture to help me create the asserts automatically to save some time in situations like this.
If you pip install pytest-mock-generator
, you would get the mock generator fixture, which is called mg
and can be used like so:
def test_open_two_files(mg):
open_mock = mock_open()
with patch('open_two_files.open', open_mock, create=True):
open_two_files("input_filepath", "output_filepath")
open_mock.assert_called_with("input_filepath", 'r')
mg.generate_asserts(open_mock)
Then execute your test and the fixture would print to the console and also copy the following to your clipboard:
from mock import call
assert 2 == open_mock.call_count
open_mock.assert_has_calls(calls=[call('output_filepath', 'w'),call('input_filepath', 'r'),])
open_mock.return_value.__enter__.assert_called_once_with()
open_mock.return_value.__iter__.assert_called_once_with()
open_mock.return_value.__exit__.assert_called_once_with(None, None, None)
You can insert the code as is to your test or modify it to your needs. Say that you don't care about testing the enter and exit from the context, your final test would look like so:
from unittest.mock import patch, mock_open, call
from open_two_files import *
def test_open_two_files():
open_mock = mock_open()
with patch('open_two_files.open', open_mock, create=True):
open_two_files("input_filepath", "output_filepath")
open_mock.assert_called_with("input_filepath", 'r')
assert 2 == open_mock.call_count
open_mock.assert_has_calls(calls=[call('output_filepath', 'w'),call('input_filepath', 'r'),])
Upvotes: 1
Reputation: 16815
If you check the documentation for assert_called_with
, you will see:
This method is a convenient way of asserting that the last call has been made in a particular way
(emphasis mine)
You can check instead for any call with these arguments by using assert_any_call:
def test_open_two_files():
open_mock = mock_open()
with patch('open_two_files.open', open_mock, create=True):
open_two_files("input_filepath", "output_filepath")
assert open_mock.call_count == 2
open_mock.assert_any_call("input_filepath", 'r')
open_mock.assert_any_call("output_filepath", 'w')
If you also want to check the sequence of the calls, you have to check each call separately:
def test_open_two_files():
...
assert open_mock.call_count == 2
assert open_mock.call_args_list[0][0] == ("output_filepath", 'w')
assert open_mock.call_args_list[1][0] == ("input_filepath", 'r')
or, if you are at least under Python 3.8:
def test_open_two_files():
...
assert open_mock.call_count == 2
assert open_mock.call_args_list[0].args == ("output_filepath", 'w')
assert open_mock.call_args_list[1].args == ("input_filepath", 'r')
(the args
and kwargs
attributes for the call args have been introduced in Python 3.8)
Upvotes: 1