Package: pytest-automock
pytest-automockReasonNo generic automock solutionFeaturesPytest pluginAutogenerate/autouse mocks for functions and objectsSync and async supportLocked mode to be sure mocked objects stay untouchedCustomizable serializationLimitationsNo support for dunder methods (can be partly solved in future)No support for sync/async generators/contextsRaces can break tests, since order countsNon-determenistic representation will break tests, since representation is a part of call snapshot keyLicensepytest-automockis offered under MIT license.Requirementspython 3.6+UsageLets say you have some modulemymod.py:importtimeclassNetwork:defget_data_from_network(self,x,y):time.sleep(1)returnx+ydefsend_data_to_network(self,value):time.sleep(1)deflogic(x):n=Network()a,b=0,1whileb<x:c=n.get_data_from_network(a,b)a,b=b,cn.send_data_to_network("ok")returnbAnd you want to create mocks for yourNetworkclass (since testing time and sane counts), but you are too lazy to write them...conftest.py:importpytestimportmymod@pytest.fixture(autouse=True)def_mocks(automock):withautomock((mymod,"Network")):yieldYou can also use module path notation:importpytest@pytest.fixture(autouse=True)def_mocks(automock):withautomock("mymod.Network"):yieldtest_logic.py:frommymodimportlogicdeftest_logic():assertlogic(7)==8assertlogic(10)==13If you runpyteston this setup, then you will see fail:$pytest-x
...
ERuntimeError:Mockislocked,but'__init__'wantedautomockcan work in two modes: locked and unlocked. Locked mode is default, real methods calls of mocked objects are
not allowed in this mode. So, above error says that we can't call__init__of ourNetwork.
In locked mode there is no mock-files update also.To allow real calls and mocks generationautomockprovides extra cli argument topytest:--automock-unlocked$pytest-x--automock-unlocked
...
test_logic.py.
...1passedin22.09sAfter that you can see thattests/mocks/test_logic/mymod/Networkfile was created. This is mock for your test sequence.
Now you can rerun tests and see what happens (you can omit--automock-unlockedkey for ensurance, that real object
will not be touched (actually even created)).$pytest-x
...
test_logic.py.
...1passedin0.04sAPIautomock(fixture)automockfixture is acontext managerdefautomock(*targets,storage:Union[str,Path]="tests/mocks",override_name:Optional[str]=None,unlocked:Optional[bool]=None,remove:Optional[bool]=None,encode:Callable[[Any],bytes]=default_encode,decode:Callable[[bytes],Any]=default_decode,debug:Optional[Callable[[Dict,Call,Optional[Call]],None]]=None)*targets: pair/tuple of object/module and attribute name (str) or module path to object/function with dot delimiter ((mymod, "Network")or"mymod.Network")storage: root path for storing mocksoverride_name: forced mock-file nameunlocked: mode selector (if omited, selected by--automock-unlocked)remove: remove test mock before test run (if omited, selected by--automock-remove)encode: encode routinedecode: decode routinedebug: function for debugging failed cases, when you do not understand why automock bakes. Arguments are:memoryfor current failed testcall_wantedwhich is a call you want to do right nowcall_savedwhich is a call you saved last time you generate mocks for this testcall_wantedandcall_savedare richCallclass objects, inspect it inmock.pyfile. Also, you can use a"pdb"string instead of your own function as adebugargument value, to use internal function withpdb.set_trace()instruction. Defaultencode/decoderoutine usespickleandgzip.automock_unlocked(fixture)Fixture with default mode from cli parameter (bool).automock_remove(fixture)Fixture with default mode from cli parameter (bool).automock(function)automockfunction is not supposed to be used by anyone butautomockfixturedefautomock(factory:Callable,*,memory:Dict,locked:bool=True,encode:Callable[[Any],bytes]=default_encode,decode:Callable[[bytes],Any]=default_decode,debug:Optional[Callable[[Dict,Call,Optional[Call]],None]]=None):factory: object/function to wrapmemory: dicrionary to get/put mockslocked: mode selectorencode: encode routinedecode: decode routinedebug: same as for fictureDefaultencode/decoderoutine usespickleandgzip.CaveatsOrderAs feature paragraph described: «order counts». What does it mean?FunctionsMocked functions/coroutines call order counts. If you mock sequencefunc(1,2)func(2,3)and trying to use mocked data with sequencefunc(2,3)func(1,2)You will get an error, since calling order is part of idea of deterministic testsObjectsMocked objects have same behavior, but methods call are individual, so if you mock sequencet1=T(1)t2=T(2)t1.func(1,2)t2.func(2,3)then calling order are individual for method calls, so this is ok:t1=T(1)t2=T(2)t2.func(2,3)t1.func(1,2)But not for__init__method, since mocks are internaly attached to instancet2=T(2)t1=T(1)t1.func(1,2)t2.func(2,3)will failFunction argumentsInternally, key for mocks consists of instance number and call number. This leads to some «unobvious» behavior:importtimefrompytest_automockimportautomockdefnop(x):returnxm={}mocked=automock(nop,memory=m,locked=False)mocked(time.time())mocked=automock(nop,memory=m,locked=True)mocked(time.time())Will fail because of argument in mock creation time differs from argument in mock use time. Same thing will break mocks if pickled representation is not determenistic.DevelopmentRun testsSince coverage issue/feature, plugins coverage is broken by default.Workaround:COV_CORE_SOURCE=pytest_automockCOV_CORE_CONFIG=.coveragercCOV_CORE_DATAFILE=.coverage.eagerpytest