2

I am trying to mock open and want to check if close gets called at least once

class MyObject():
    def __init__(self,path):
        fp = open(path)
        self.file_list = []
        for line in fp:
            self.file_list.append(line.strip())
        fp.close()   



def testsimpleFile():
    fake_file = io.StringIO("data.csv\ndata2.csv")
    with patch("builtins.open",return_value=fake_file,create=True) as mock_file:
        f = MyObject("path/to/open/test.f")
        mock_file.assert_called_once_with("/path/to/open/test.f")
        golden_list = ["data.csv","data2.csv"]
        assert f.file_list == golden_list

This is my working testcode until now and now i want to additionally check if the the close method was called i tried to add

mock_file.close.assert_called_once()

and

mock_file.fake_file.close.assert_called_once()

but both will not catch the method call.

TM90
  • 680
  • 7
  • 21
  • 1
    Can you post the code for the `MyObject` class as well please? It may be the case that `.close` _isn't_ called... – wholevinski Oct 09 '18 at 14:49
  • close gets called. I will tomorrow write a minimal example of the MyObject since it is a larger Class in the real application. – TM90 Oct 09 '18 at 14:56
  • 1
    Actually, I might see what the problem is. Change that to `mock_file.return_file.close.assert_called_once()`. EDIT: That probably won't work because your return value is `fake_file`, not a mock object. If you go ahead and make that change and get a 'No method "assert_called_once" on io.StringIO', let me know and I think I can write up an answer to accomplish what you're looking for. – wholevinski Oct 09 '18 at 14:58
  • ok mock_file.fake_file.close.assert_called_once() does not resultet in no method assert_called_once it resultet in an assertion since again it did not notice the close() method call. I updated the question with a minimal example – TM90 Oct 09 '18 at 19:37
  • That was a typo on my part, and it looks like you might've taken my typo and made one too. I meant to write `mock_file.return_value.close.assert_called_once()`. Regardless, I'll go ahead and post an answer I think might work for you. – wholevinski Oct 10 '18 at 10:14

1 Answers1

1

The short of it is: You can't track that the function is being called with assert_called_once if the return value of open isn't a mock object. So, instead of making the return value a StringIO we can make it a MagicMock that will act like a file handle.

import io
from unittest.mock import patch, MagicMock

class MyObject():
    def __init__(self,path):
        fp = open(path)
        self.file_list = []
        for line in fp:
            self.file_list.append(line.strip())
        fp.close()   

def testsimpleFile():
    fake_file = MagicMock()
    fake_file.__iter__.return_value = ["data.csv", "data2.csv"]
    with patch("builtins.open", return_value=fake_file, create=True) as mock_file:
        f = MyObject("/path/to/open/test.f")
        mock_file.assert_called_once_with("/path/to/open/test.f")
        golden_list = ["data.csv", "data2.csv"]
        assert f.file_list == golden_list
        fake_file.close.assert_called_once()
wholevinski
  • 3,658
  • 17
  • 23
  • Thank you that works. The only thing I had to modify is that the return value for the fake file should be StringIO("data.csv/ndata2.csv"). – TM90 Oct 10 '18 at 11:14
  • @TM90 So, I considered using a `side_effect` to split on `\n` and return an iterable, but at that point you're basically testing that `__iter__` on `open`'s return value works properly. I think that's a thing you can take for granted and not worry about testing. You want to mock the file for the way you're using it, not completely test `builtins.open`. – wholevinski Oct 10 '18 at 11:25