diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml
index 9727c2a..478c152 100644
--- a/.github/workflows/publish-to-pypi.yml
+++ b/.github/workflows/publish-to-pypi.yml
@@ -3,8 +3,9 @@ on: push
jobs:
build-and-publish-test:
- name: Build and publish python distribution to PyPI
+ name: Build and publish python distribution to test PyPI
runs-on: ubuntu-18.04
+ if: "!(startsWith(github.event.ref, 'refs/tags') || github.ref == 'refs/heads/master')"
continue-on-error: true
steps:
- name: Checkout Code
@@ -41,7 +42,7 @@ jobs:
if [ "${INSTALLED:23}" != "$EXPECTED" ]; then exit 1; fi
build-and-publish-production:
- name: Build and publish python distribution to PyPI
+ name: Build and publish python distribution to production PyPI
runs-on: ubuntu-18.04
if: startsWith(github.event.ref, 'refs/tags')
steps:
diff --git a/README.md b/README.md
index 47bfd3b..3e00fc4 100644
--- a/README.md
+++ b/README.md
@@ -21,12 +21,152 @@ A pytest fixture wrapper for https://pypi.org/project/mock-generator
pip install pytest-mock-generator
```
-or install with `Poetry`
+or install with [poetry](https://github.com/python-poetry/poetry):
```bash
poetry add pytest-mock-generator
```
+## Usage
+This [pytest plugin](https://docs.pytest.org/en/latest/how-to/writing_plugins.html)
+adds the `mg` [fixture](https://docs.pytest.org/en/latest/reference/fixtures.html#fixture)
+which helps when writing tests that use [python mocks](https://docs.python.org/3.7/library/unittest.mock.html).
+
+Let's start with an easy example. Assume you have a module named `tested_module.py` which holds a function
+to process a string sent to it and then add it to a zip file:
+```python
+import zipfile
+
+def process_and_zip(zip_path, file_name, contents):
+ processed_contents = "processed " + contents # some complex logic
+ with zipfile.ZipFile(zip_path, 'w') as zip_container:
+ zip_container.writestr(file_name, processed_contents)
+```
+This is the unit under test, or UUT.
+
+Although this is a very short function,
+writing the test code takes a lot of time. It's the fact that the function uses
+a context manager makes the testing more complex than it should be.
+*If you don't believe me, try to manually write mocks and asserts which verify
+that `zip_container.writestr` was called with the right parameters.*
+
+In any case, you start with a test skeleton:
+
+```python
+from tests.sample.code.tested_module import process_and_zip
+
+def test_process_and_zip(mocker, mg):
+ # Arrange: todo
+
+ # Act: invoking the tested code
+ process_and_zip('/path/to.zip', 'in_zip.txt', 'foo bar')
+
+ # Assert: todo
+```
+Now it's time to use Mock Generator instead of manually writing the 'Arrange'
+and 'Assert' sections.
+
+### Generating the 'Arrange' section
+To generate the 'Arrange' section, simply put this code at the beginning of
+your test function skeleton and run it (make sure to add the `mg` fixture to
+your test function):
+```python
+mg.generate_uut_mocks(process_and_zip)
+```
+This will generate the 'Arrange' section for you:
+```python
+# mocked dependencies
+mock_ZipFile = mocker.MagicMock(name='ZipFile')
+mocker.patch('tests.sample.code.tested_module.zipfile.ZipFile', new=mock_ZipFile)
+```
+The generated code is returned, printed to the console and also copied to the
+clipboard for your convenience.
+Just paste it (as simple as ctrl+V) at the start of your test function:
+```python
+from tests.sample.code.tested_module import process_and_zip
+
+def test_process_and_zip(mocker, mg):
+ # mocked dependencies
+ mock_ZipFile = mocker.MagicMock(name='ZipFile')
+ mocker.patch('tests.sample.code.tested_module.zipfile.ZipFile', new=mock_ZipFile)
+
+ # Act: invoking the tested code
+ process_and_zip('/path/to.zip', 'in_zip.txt', 'foo bar')
+
+ # Assert: todo
+```
+
+Excellent! Arrange section is ready.
+
+### Generating the Assert section
+Now it's time to add the asserts. Add the following code
+**at the 'Assert'** step:
+```python
+mg.generate_asserts(mock_ZipFile)
+```
+The `mock_ZipFile` is the mock object you generated earlier.
+Now execute the test function to get the assert section:
+```python
+assert 1 == mock_ZipFile.call_count
+mock_ZipFile.assert_called_once_with('/path/to.zip', 'w')
+mock_ZipFile.return_value.__enter__.assert_called_once_with()
+mock_ZipFile.return_value.__enter__.return_value.writestr.assert_called_once_with('in_zip.txt', 'processed foo bar')
+mock_ZipFile.return_value.__exit__.assert_called_once_with(None, None, None)
+```
+Wow, that's a handful of asserts! Some are very useful:
+* Checking that we opened the zip file with the right parameters.
+* Checking that we wrote the correct data to the proper file.
+* Finally, ensuring that `__enter__` and `__exit__` are called, so there
+are no open file handles which could cause problems.
+
+You can remove any generated line which you find unnecessary.
+
+Paste that code right after the act phase, and you're done!
+
+The complete test function:
+```python
+from tests.sample.code.tested_module import process_and_zip
+
+def test_process_and_zip(mocker):
+ # mocked dependencies
+ mock_ZipFile = mocker.MagicMock(name='ZipFile')
+ mocker.patch('tests.sample.code.tested_module.zipfile.ZipFile', new=mock_ZipFile)
+
+ # Act: invoking the tested code
+ process_and_zip('/path/to.zip', 'in_zip.txt', 'foo bar')
+
+ assert 1 == mock_ZipFile.call_count
+ mock_ZipFile.assert_called_once_with('/path/to.zip', 'w')
+ mock_ZipFile.return_value.__enter__.assert_called_once_with()
+ mock_ZipFile.return_value.__enter__.return_value.writestr.assert_called_once_with('in_zip.txt', 'processed foo bar')
+ mock_ZipFile.return_value.__exit__.assert_called_once_with(None, None, None)
+```
+Can you imagine the time it would have taken you to code this on your own?
+
+### What's Next
+#### Asserting Existing Mocks
+At times, you may be editing a test code already containing mocks, or
+you choose to write the mocks yourself, to gain some extra control.
+
+Mock Generator can generate the assert section for standard
+Python mocks, even if they were not created using the Mock Generator.
+
+Put this after the 'Act' (replace `mock_obj` with your mock object name):
+```python
+mg.generate_asserts(mock_obj)
+```
+Take the generated code and paste it at the 'Assert' section.
+
+#### Generating the 'Arrange' and 'Assert' sections in one call
+You can make the `generate_uut_mocks_with_asserts` call create the
+`generate_asserts` code for you (instead of having to call
+`generate_uut_mocks`):
+```python
+mg.generate_uut_mocks_with_asserts(function_under_test)
+```
+
+## More information
+Additional documentation can be found in the [mock-generator pypi](https://pypi.org/project/mock-generator).
## 📈 Releases
diff --git a/pyproject.toml b/pyproject.toml
index 563887c..4a456a8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -5,7 +5,7 @@ build-backend = "poetry.core.masonry.api"
[tool.poetry]
name = "pytest-mock-generator"
-version = "0.3.0"
+version = "1.0.0"
description = "A pytest fixture wrapper for https://pypi.org/project/mock-generator"
readme = "README.md"
authors = ["Peter Kogan "]
@@ -32,7 +32,7 @@ classifiers = [
"Topic :: Software Development :: Testing",
'Framework :: Pytest',
"License :: OSI Approved :: MIT License",
- "Development Status :: 3 - Alpha",
+ "Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Operating System :: OS Independent",
]