1

Say I have a method that gets passed some paths, reads the files at each of these paths, then returns a dictionary from file name to file content, like so:

from contextlib import ExitStack
from pathlib import Path


class ClassToTest:
    def method_to_test(self, *paths: Path):
        with ExitStack() as stack:
            files = [stack.enter_context(path.open(mode='r')) for path in paths]
            return {f.name: f.read() for f in files}

Now say I want to test that if I pass in e.g. C:\wherever\file_name.xyz that the returned dictionary contains the key file_name.xyz. Since my method under test is opening and reading files, I want to mock out the Path object. I think I can do something like:

from unittest.mock import Mock, mock_open

class Tests:
    def test(self):
        mock_path = Mock(spec=Path)
        mock_path.open = mock_open()
        # ???

        files = ClassToTest().method_to_test(mock_path)
        assert 'file_name.xyz' in files

But I can't figure out how to get f.name (i.e. mock_path.open().name) to return file_name.xyz.

UtterlyConfused
  • 983
  • 1
  • 10
  • 18
  • Depending on how heavily you rely on the file system, you could use [pyfakefs](https://github.com/jmcgeheeiv/pyfakefs) to mock out the complete filesystem (yes, I'm a contributor to that...) - that would allow you to create fake files and test both the file names and contents of the files. – MrBean Bremen Feb 28 '20 at 18:02

1 Answers1

3

Try assert mock_path.open().name in files. Should just work as you are using the mocking library. It'll be a fragile test though as mock_path.name != mock_path.open().name

If you're using pytest, then just use the tmp_path fixture which is unique for each test invocation.

import pytest

def f(path):
    with path.open() as f:
        return {f.name: f.read()}

def test_f(tmp_path):
    name = 'some_file.txt'
    content = 'hello\nworld'
    path = tmp_path / name
    path.write_text(content)

    result = f(path)

    assert str(path) in result
    assert content in result.values()

def test_g(tmp_path):
    assert list(tmp_path.iterdir()) == []
Dunes
  • 37,291
  • 7
  • 81
  • 97
  • say for the sake of argument that I wanted `mock_path.open().name` to actually return `'file_name.xyz'`? – UtterlyConfused Mar 17 '20 at 17:00
  • Just assign to it. `mock_open` creates a callable mock. Mocks always return the same objects for each attribute unless you use the `side_effect` parameter. So you can just do `mock_path.open().name = 'xyz'`, and then future calls to `mock_path.open().name` will return `'xyz'`. – Dunes Mar 17 '20 at 20:10