简体   繁体   中英

How to write a pytest to test for opening two separate files in a single function

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?

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)

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'),])

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM