Skip to main content

pytest-expectdir

tests codecov

This pytest plugin provides an easy way to test file generation and file-system transformation.

Install

pip install pytest-expectdir

Usage

Here is the workflow :

Create a directory containing files and directories expected to be generated, and optionally one with your initial data so that it looks like this :

my_pkg/
├ my_pkg/
│ └ ...
└ tests/
  ├ test_feature.py
  └ test_feature/
    ├ initial (optional)/
    │ └ ... your initial data
    └ expected/
      └ ... expected output tree

Then you write your test as follow :

test_feature.py

def test_feature(expectdir):
  with expectdir('test_feature') as output_dir:
    # Do whatever you want inside output_dir, which is a temporary directory copied from initial/
  # At the end of the with, output_dir gets compared with expected
  # ...And you get a fancy report of the difference if there are (as an AssertionError).

Note that you can also pass manually the keyword arguments initial and expected to expectdir. If, for example, you have multiple tests ending up with the same expected result, or with the same initial one.

The following is equivalent to the previous example :

def test_feature(expectdir):
  with expectdir(initial='data_test_feature/initial', expected='data_test_feature/expected') as output_dir:
    # ...

If your test data follows this schema :

tests/
├ test_feature.py
└ test_feature/
  └ TestCaseClassName (if one)/
    └ test_method
      ├ initial (optional)/
      │ └ ...
      └ expected
        └ ...

(like the first example), then you can even omit the parameters :

def test_feature(expectdir):
  with expectdir() as output_dir:
    # ...

API

(pytest.fixture) expectdir(datapath=None, *, initial=None, expected=None, current_dir_replace_string=None) -> contextmanager as outputDir:Path

The main fixture. Its value is a function that returns a context manager. The context manager will return (when opened) a path to a temporary directory that will get compared to the Expected directory at closing. An AssertionError will then be raised if the two directory are not the same. .gitkeep files, conventionally used to keep empty directories are ignored.

You also may require to the content of some file containing the path where the test is executed. Just before executing what is in the with, the string passed to current_dir_replace_string is replaced by temporary directory path in all files in initial/. Also, after the with block, and before checking the temporary directory is equal to the expected one, all occurences of the temporary directory path is replaced by current_dir_replace_string. If None is passed, no replacement is done.

The function chooses an optional initial directory and a required expected directory as follow :

Expected

  • If the expected keyword argument is provided, it's this directory that will be used.
  • Else, if the datapath positional argument is provided, expected will be datapath/"expected".
  • Else, the test path will be used as fallback, i.e. currentModuleDirectory/TestCaseClassName/test_method/expected if inside a testCase class, else, currentModuleDirectory/test_function/expected if the test is a standalone function.
  • If the selected path does not exist, raises a FileNotFoundError.

Initial

  • If the initial keyword argument is provided and equal to __empty__, then the initial directory will be empty.
  • If the initial keyword argument is provided and is a different string or a Path instance, it's this directory that will be used.
  • Else, if the datapath positional argument is provided, expected will be datapath/"initial".
  • Else, if the expected keyword argument is not provided, the test path will be used as fallback, i.e. currentModuleDirectory/TestCaseClassName/test_method/initial if inside a TestCase class, else, currentModuleDirectory/test_function/initial if the test is a standalone function.
  • Else, the initial directory will be empty.
  • If the initial keyword argument is a Path, and this path does not exists, raises a FileNotFoundError.

(pytest.fixture) expectdir(datapath=None, *, initial=None, expected=None, current_dir_replace_string="{{current_directory}}") -> contextmanager as outputDir:Path

Equivalent to expectDir, but with "{{current_directory}}" as default value for current_dir_replace_string.

cmpdir(candidate:Path, expected:Path) -> Tuple[result:bool, Tuple[candidate_only:list[Path], expected_only:list[Path], different:list[Path]]]

Compare two directories recursively, and list files only in the first, only on the second, and in both but different.

The result is True if the directories are identical.

When a subdirectory is present only in one of the compared directories, only the subdirectory itself is listed (not all its content).

Files .gitkeep are ignored.

formatDiff(file_output:TextIO, candidate:Path, expected:Path, diffRes:Tuple[candidate_only:list[Path], expected_only:list[Path], different:list[Path]]) -> None

Takes the result of cmpdir, and print to file_output the diff summary.

formatFileDiff(file_output:TextIO, lines_candidate:Iterable[str], lines_expected:Iterable[str], context=3, indent=' ') -> None

Format the diff of two files, and output to file_output. context is the number of identical to show before and after insertion / deletion for context. indent is the line prefix, so that the output is indented.

How Fancy ?

Here is a sample from the tests :

In [1]: import sys
   ...: from pytest_expectdir.plugin import cmpdir, formatDiff
   ...: initial = './tests/data/test3/initial/'
   ...: expected = './tests/data/test3/expected/'
   ...: formatDiff(sys.stdout, initial, expected, cmpdir(initial, expected)[1])
Directory ./tests/data/test3/expected/ (expected) is different from ./tests/data/test3/initial/ (candidate).
Missing in candidate :
dir3/
f1
Extra in candidate :
dir2/
f4
In both directories but different content:

f3:
  - This line is removed
  - And this one too
    This is a complex test
  - Hello 3
  + Hello 3 And replaced ones
    With some lines
  + And added lines
    And otherlines
    common 1
    common 2
  [...] --- expected:11 / candidate:10 ---
    common 6
    common 7
    common 8
  - and diff 1
  + diff

dir4/f3:
  - This line is removed
  - And this one too
    This is a complex test
  - Hello 3
  + Hello 3 And replaced ones
    With some lines
  + And added lines
    And otherlines
    common 1
    common 2
  [...] --- expected:11 / candidate:10 ---
    common 6
    common 7
    common 8
  - and diff 1
  + diff

Preview Image

1.2.0

Added Replacement string for the current directory

1.1.4

Fix typo

1.1.3

Fix Coverage.io badge in README

1.1.2

Added unit test for full coverage

1.1.1

Fix Exception not forwarded

1.1.0

Fallback for expectdir to module_stem/class(if one)/function if datadir is None

1.0.0

expectdir -> (datapath=None, *, initial=None, expected=None) -> contextmanager -> tmpdir:Path fixture

Release files for pytest-expectdir 1.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pytest-expectdir 1.2.0
File Size Uploaded
pytest-expectdir-1.2.0.tar.gz 9.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pytest-expectdir 1.2.0
File Interpreter ABI Platform
pytest_expectdir-1.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 18.0 kB

Release files / pytest-expectdir-1.2.0.tar.gz

Download URL pytest-expectdir-1.2.0.tar.gz
Size 9.8 kB
Tags Source
SHA-256 checksum
How to use checksums
145b7f86d4e8ae7c7cace3007f32d68afbdd4d18c392b3d5792794f8e9d51fae
BLAKE2b-256 checksum
How to use checksums
c44cdcaf2bf29036e3feb616edd32056be46332f46378d4d72d282af606abdfa
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/4.0.2 CPython/3.10.10

Release files / pytest_expectdir-1.2.0-py3-none-any.whl

Download URL pytest_expectdir-1.2.0-py3-none-any.whl
Size 8.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8fa19e7c0b69d6bfd0e81c6e33c9a8283eb14a7bab8408067e430a827a03f993
BLAKE2b-256 checksum
How to use checksums
2c84fc3c513a95705e39992b007a9cf641af963249c1b4c9cf8857dca9a6e6f6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/4.0.2 CPython/3.10.10

Release history Release notifications | RSS feed

This release

1.2.0 This release

2 release files

1.1.4

2 release files

1.1.3

2 release files

1.1.2

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page