Package: mock-generator
Mock GeneratorA tool to generate the basic mocks and asserts for faster unit testing.🎉 New: you can now usepytest-mock-generator, for more fluid pytest development. See documentation later inthis doc.IntroductionA typical unit test looks like this (AAA pattern):Arrange – Setup and prepare the various objects and prerequisites.Act – Invoke the tested code by calling the tested function.Assert – Verify the outcome. This can be the return value of the tested
function and/or some side effects.When using mocks, much time is wasted on the wiring.
TheArrangeandAssertsections are notorious in that regard.
Only a fraction of the time is spent on the actual logic of the test.This tool is meant to save you time,
by generating theArrangeandAssertsections for you.
The generated code can then be used as is, or altered according to your needs.UsageNote: All examples assume usage of thepytest-mockwhich is a fixture forpytest.Getting StartedLet's assume you have a module namedtested_module.pywhich holds a function
to process a string sent to it and then add it to a zip file:importzipfiledefprocess_and_zip(zip_path,file_name,contents):processed_contents="processed "+contents# some complex logicwithzipfile.ZipFile(zip_path,'w')aszip_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
thatzip_container.writestrwas called with the right parameters.In any case, you start with a test skeleton:importmock_autogenfromtests.sample.code.tested_moduleimportprocess_and_zipdeftest_process_and_zip(mocker):# Arrange: todo# Act: invoking the tested codeprocess_and_zip('/path/to.zip','in_zip.txt','foo bar')# Assert: todoNow it's time to use Mock Generator instead of manually writing the 'Arrange'
and 'Assert' sections.Generating the 'Arrange' sectionTo generate the 'Arrange' section, simply put this code at the beginning of
your test function skeleton and run it:mock_autogen.generate_uut_mocks(process_and_zip)This will generate the 'Arrange' section for you:# mocked dependenciesmock_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:importmock_autogenfromtests.sample.code.tested_moduleimportprocess_and_zipdeftest_process_and_zip(mocker):# mocked dependenciesmock_ZipFile=mocker.MagicMock(name='ZipFile')mocker.patch('tests.sample.code.tested_module.zipfile.ZipFile',new=mock_ZipFile)# Act: invoking the tested codeprocess_and_zip('/path/to.zip','in_zip.txt','foo bar')# Assert: todoExcellent! Arrange section is ready.Generating the Assert sectionNow it's time to add the asserts. Add the following codeat the 'Assert'step:mock_autogen.generate_asserts(mock_ZipFile)Themock_ZipFileis the mock object you generated earlier.
Now execute the test function to get the assert section:assert1==mock_ZipFile.call_countmock_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:fromtests.sample.code.tested_moduleimportprocess_and_zipdeftest_process_and_zip(mocker):# mocked dependenciesmock_ZipFile=mocker.MagicMock(name='ZipFile')mocker.patch('tests.sample.code.tested_module.zipfile.ZipFile',new=mock_ZipFile)# Act: invoking the tested codeprocess_and_zip('/path/to.zip','in_zip.txt','foo bar')assert1==mock_ZipFile.call_countmock_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 NextUsing pytest-mock-generatorYou can now usepytest-mock-generatorto generate mocks:pipinstallpytest-mock-generatorThen usemg fixturein your tests, instead of having toimport mock_autogen,
like so:fromtests.sample.code.tested_moduleimportprocess_and_zipdeftest_process_and_zip(mocker,mg):# Arrange: todo# replace with the output of the below commandmg.generate_uut_mocks(process_and_zip)# Act: invoking the tested codeprocess_and_zip('/path/to.zip','in_zip.txt','foo bar')# Assert: todoAsserting Existing MocksAt 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' (replacemock_objwith your mock object name):importmock_autogenmock_autogen.generate_asserts(mock_obj)Take the generated code and paste it at the 'Assert' section.Generating the 'Arrange' and 'Assert' sections in one callYou can make thegenerate_uut_mocks_with_assertscall create thegenerate_assertscode for you (instead of having to callgenerate_uut_mocks):importmock_autogenmock_autogen.generate_uut_mocks_with_asserts(function_under_test)Mocking EverythingSo far you have seen examples of mocking the dependencies of a single function.
Mock Generator can generate mocks for objects, classes and entire modules!A great way to learn about those capabilities is to see them in action:frommock_autogenimportPytestMockerPytestMocker(a_module_or_a_class_you_test_and_want_to_mock_its_dependencies).mock_everything().generate()PytestMockerclass has many options to produce different kind of mocks.
See its documentation for further details.TroubleshootingNo output is printed to the console when running PytestBe sure to run Pytest with appropriate flags to print the output:pytest -rA.No output is copied to the clipboardMock Generator usespyperclipfor clipboard
manipulations, look for errors / warnings printed in the console, similar to this one:WARNING mock_autogen.utils:utils.py:28 
Could not copy func results to clipboard Traceback (most recent call last): 
    File "/home/username/Documents/code/test_mock_gen/.envtest/lib/python3.6/site-packages/mock_autogen/utils.py", line 25, in to_clipboard 
        pyperclip.copy(result) 
    File "/home/username/Documents/code/test_mock_gen/.envtest/lib/python3.6/site-packages/pyperclip/__init__.py", line 659, in lazy_load_stub_copy 
        return copy(text) 
    File "/home/username/Documents/code/test_mock_gen/.envtest/lib/python3.6/site-packages/pyperclip/__init__.py", line 336, in __call__
        raise PyperclipException(EXCEPT_MSG) pyperclip.PyperclipException: 

    Pyperclip could not find a copy/paste mechanism for your system. 
    For more information, please visit https://pyperclip.readthedocs.io/en/latest/index.html#not-implemented-errorIt's possible that your system lacks some dependency, visithttps://pyperclip.readthedocs.io/en/latest/index.html#not-implemented-errorfor
additional troubleshooting steps.Wrapping upI hope that you were convinced that this tool can save you a lot of time.If you have improvement suggestions, bug reports,
or would like to contribute pull requests, let me know.