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

ghz 昨天 ⋅ 5 views

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?

Answer

The issue you're encountering is due to how the patch context manager is replacing the built-in open function. The mock_open object only tracks the last open call made within the patched context. Therefore, after the open function is called for the input file, the mock will track it, and when you call open for the output file, it overwrites the mock, so only the last call is checked.

To solve this and track all open calls, you can use mock_open and set it up to track the calls to both files separately. To do this, you can use patch to mock the open function and capture both calls separately.

Here’s an updated version of your test:

Updated Test: test_open_two_files.py

import pytest
from unittest.mock import patch, mock_open
from open_two_files import open_two_files

def test_open_two_files():
    # Mock for both input and output files
    mock_input = mock_open()
    mock_output = mock_open()

    with patch('builtins.open', side_effect=[mock_input, mock_output]):
        open_two_files("input_filepath", "output_filepath")
    
    # Assert the input file is opened in 'r' mode
    mock_input.assert_called_once_with("input_filepath", 'r')
    
    # Assert the output file is opened in 'w' mode
    mock_output.assert_called_once_with("output_filepath", 'w')

Explanation:

  1. mock_open(): We create two mock objects: one for the input file (mock_input) and one for the output file (mock_output).
  2. side_effect: The side_effect argument of patch allows us to specify different mock objects for consecutive calls to open. In this case, the first call to open will use mock_input, and the second will use mock_output. This simulates the two different file opens in your open_two_files function.
  3. Assertions:
    • We assert that mock_input was called with the correct input filepath and 'r' mode.
    • We assert that mock_output was called with the correct output filepath and 'w' mode.

Why This Works:

  • The side_effect allows us to provide multiple mock objects in sequence, so every time open is called, it uses the next mock in the list.
  • Each mock tracks the calls to open, so we can check that both the input and output files were opened correctly without overwriting each other.

Alternative Approach:

If you prefer to track all calls to open without using side_effect, you could mock the open function with a single mock and manually check its call arguments:

import pytest
from unittest.mock import patch, mock_open
from open_two_files import open_two_files

def test_open_two_files():
    # Create a single mock
    open_mock = mock_open()

    with patch('builtins.open', open_mock):
        open_two_files("input_filepath", "output_filepath")

    # Assert that open was called with the correct arguments for both files
    calls = open_mock.mock_calls
    assert calls[0] == ('input_filepath', 'r')
    assert calls[1] == ('output_filepath', 'w')

Here, mock_calls contains a list of all calls made to the mocked open function, and you can check each one individually. This approach is simpler if you're mocking a single open function but still want to check both file paths.