0

I have a method that raises an error if any cells in a csv row are blank. However the test does not assert that the error has raised, even though the method does raise it and logs it. I think it has something to do with it being in a try/except but I am not sure. I looked at this but couldn't get it to work.

pytest output

====================================================================================================== FAILURES ======================================================================================================
___________________________________________________________________________________ TestCSVFileMethods.test_verify_cell_not_blank ____________________________________________________________________________________

    self = <tests.test_csv_methods.TestCSVFileMethods testMethod=test_blank_cell>
    
        def test_blank_cell(self):
            # Add another entry to the name column with a blank value
            with open(self.temp_file, "a", newline="") as f:
                writer = csv.DictWriter(f, fieldnames=["id","name"])
                writer.writerow({"id": "2", "name": ""})
    
    >       with pytest.raises(BlankCellError):
    E       Failed: DID NOT RAISE <class 'src.csv_methods.BlankCellError'>
    
    tests/test_csv_methods.py:161: Failed
    ------------------------------------------------------------------------------------------------ Captured stdout call ------------------------------------------------------------------------------------------------
    BlankCell: Missing cell value for ID: '2' - 'Column: name'
    ------------------------------------------------------------------------------------------------- Captured log call --------------------------------------------------------------------------------------------------
    WARNING  root:csv_methods.py:294 Missing cell value for ID: '2' - 'Column: name'
    Missing cell value for ID: '2' - 'Column: name'
    ============================================================================================== short test summary info ===============================================================================================
    FAILED tests/test_csv_methods.py::TestCSVFileMethods::test_blank_cell - Failed: DID NOT RAISE <class 'src.csv_methods.BlankCellError'>
    ============================================================================================ 1 failed, 6 passed in 0.10s =============================================================================================

Error and Method

class BlankCellError(Exception):
        pass

csv_file = "csv_data/assets.csv"
def blank_cell(csv_file):
    try:
        with open(csv_file, newline="") as file:
            reader = csv.DictReader(file)

            for row_idx, row_v in enumerate(reader, start=1):  # Start at 1 to skip the csv column headers
                for k, v in row_v.items():
                    if v.strip() == "":
                        raise BlankCellError(f"Missing cell value for ID: '{row_v['id']}' - 'Column: {k}'")
                    else:
                        continue

    except BlankCellError as e:
        print(f"BlankCell: {e}")

Test

import csv
import unittest
import pytest
    
        def create_test_file(file_name, content):
            with open(file_name, "a") as f:
                writer = csv.DictWriter(f, fieldnames=["id","name"])
                writer.writeheader()
                writer.writerows(content)
        
        
        class TestCSVFileMethods(unittest.TestCase):
            def setUp(self):
                self.data = [
                    {
                        "id": "1",
                        "name": "name1"
                    },
                ]
                self.temp_file = os.path.join("tests", "tmp_csv.csv")
                create_test_file(self.temp_file, self.data)
        
            def blank_cell(self):
                    with open(self.temp_file, "a", newline="") as f:
                        writer = csv.DictWriter(f, fieldnames=["id","name"])
                        writer.writerow({"id": "2", "name": ""})
            
                    with pytest.raises(BlankCellError):
                        blank_cell(self.temp_file)

UPDATE

Based on the feedback I changed the test to this but I still get an error:

def test_blank_cell(self):
        # Add another entry to the name column with a blank value
        with open(self.temp_file, "a", newline="") as f:
            writer = csv.DictWriter(f, fieldnames=fieldnames)
            writer.writerow({"id": "2", "name": ""})

        with pytest.raises(BlankCellError) as excinfo:  
            blank_cell(self.temp_file)  
        assert str(excinfo.value) == "Missing cell value for ID"  

Error after update

The only change in the test error output:

E       Failed: DID NOT RAISE <class 'src.csv_methods.BlankCellError'>
2
  • 2
    Your method does not raise the error - it catches it itself, so a client (including pytest) will never see it. The only thing you can test here is the print output, which is the actual side effect of the function. Commented Dec 11, 2023 at 18:28
  • Your test is correct, and detects that the method that it is testing has a bug. Everything is working as expected. Commented Dec 12, 2023 at 17:17

1 Answer 1

1

This question comes up every now and then, but I have unsuccessfully searched for a duplicate that actually has an answer, so I'll answer it here.

To reiterate - this is about this common idiom in Python:

def my_function(param):
   try:
       if not is_valid(param):
           raise InvalidParamsException("foo")
           # lots of code, that may raise the same exception elsewhere
   except InvalidParamsException as e:
       print(f"my_function failed: {e}")

e.g. the code raises an exception that is caught in the same function. The exception handler does something like logging the exception, and the exception is not propagated.

For the client of the function that means that the function never raises that specific exception. The side effect of the function failing this way is only the logging, and possibly some expected state change that did not happen. These side effects are the only things that need to be tested - everything else (including the raised and caught exception) are implementation details that may change and are simply irrelevant.

Pytest also being a client of that function does not see the exception either, so using with pytest.raises correctly fails. In principle it is still possible to test if the exception is raised internally by patching the exception class. Don't do this! There is no reason to test this, as already mentioned. In this example (that matches the question) it is sufficient to check that print is called with the expected argument, e.g. something like:

@patch('builtins.print')
def test_my_function(mocked_print):
    my_function(invalid_param)    
    mocked_print.assert_called_once_with("my_function failed: foo")

for the simplified case above.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.