Package: elmock
elmockA simple python package to help dynamic mock class creation. This package is inspired
bygolang testify/mock tool.Installpip install elmockpipenv install --dev elmockHow to useCreate you mock instanceTo create a new mocked instance, you just need to create your class and inherit fromelmock.Mockone.Then implement function you need to mock with their correct signature and call class method:executewith the method
name and arguments.Execute will then look for correct method call and return or raise expected result when called.When no calls matched provided arguments, explicit errors are raises to indicate witch method call failed, and the
arguments passed to call.fromelmockimportMockfromtypingimportAnyclassMockedInstance(Mock):defmy_method(self,arg1:str,arg2:Any,kwarg1:bool=False,kwarg2:Any=None)->Any:returnself.execute('my_method',arg1,arg2,kwarg1=kwarg1,kwarg2=kwarg2)Use mocked instance in testExpect mockWhile using your mocked instance, you will have to tell witch method are expected to be called. To do so, you'll have to
use theonmethod.ontakes the method name as a string then the arguments and named arguments expected to be passed. It then returns a
MockedCallVariable matchersIf you do not need to match an absolute value or do not have strict control on some values passed to a mocked function, you can used wider matchers.ANYlet any thing pass as argumentAnyTypedcheck if argument is in asked typeAnyStrMatchingmatch argument against a regular expression. If argument is not a string, it will try to convert it before running.You can also define your own argument matcher by extending the classMock.ParameterMatcher:classParameterMatcher(ABC):@staticmethod@abstractmethoddefvalidate(parameter:Any)->bool:"""Validate parameter against some rules."""raiseNotImplementedError()Custom matcher example:classMatchDict(Mock.ParameterMatcher):@staticmethod@abstractmethoddefvalidate(parameter:Any)->bool:returnisintance(parameter,dict)and"test"inparameterandparameter["test"]Configure MockedCallAfteronmethod is called, you'll need to configure return or raises values. You will also be able to indicate an
expected number of call.returns: set return value for call. You can provide anything you wish, but it has to be a single value. /!\ call is
destructive: all previous raises or returns values will be overwritten.raises: set exception to raise. /!\call is destructive: all previous raises or returns values will be overwritten.once: indicates call is expected once.twice: indicates call is expected twice.times(X): indicates call is expected X times.Full filled assertionsWhile previous steps garantie a maximum amount of called and ensure mock returned correct value when expected, it does
not ensure every mock defines was called or if it was really called X time.To do so, you can use:assert_full_filledon the mocked class. It will raise aNotFullFilledexception if calls are found who where
either not called or called lesser than expected.calledon any MockedCall. It will check if call was used.full_filledon any MockedCall. It will check if call was used the expected times.ResetTo avoid border effects between test or if you wish to clean up mocks declared in a test, you can callresetmethod.
Best way to ensure it is done within each test case is:@pytest.fixture(autouse=True)def__cleanup_mocks(tmpdir):yieldmocked.reset()Full examplefromtypingimportAnyimportpytestfromsrc.elmockimportMock,UnexpectedMethodfromsrc.elmock.exceptionimportNotFullFilled,UnexpectedArguments,UnexpectedCallclassMocker(Mock):deftest_smtg(self,p1:str,kp1:Any=None):returnself.execute("test_smtg",p1,kp1=kp1)deftest_no_args_no_return(self):self.execute("test_no_args_no_return")deftest_no_args(self)->Any:returnself.execute("test_no_args")deftest_no_return(self,p1:str,kp1:Any=None):self.execute("test_no_return",p1,kp1=kp1)mocked=Mocker()@pytest.fixture(autouse=True)def__cleanup_mocks(tmpdir):# Setup: fill with any logic you wantyield# run test# Teardown : fill with any logic you wantmocked.assert_full_filled()mocked.reset()classTestMock:classTestNormalUsages:deftest_should_execute_nothing_for_empty_method(self):mocked_call=mocked.on("test_no_args_no_return")mocked.test_no_args_no_return()assertmocked_call.called()assertmocked_call.full_filled()deftest_should_return_expected_values(self):initial=mocked.on("test_no_args").returns("This is a test")assertmocked.test_no_args()=="This is a test"# Creating a new instance without number should not be usedmocked.on("test_no_args").returns("This is another test")assertmocked.test_no_args()=="This is a test"# Calling .returns again on same initial call should replace itinitial.returns("This is another test")assertmocked.test_no_args()=="This is another test"deftest_normal_main_use_case(self):mocked.on("test_smtg","val",kp1=True).returns("This is a test")assertmocked.test_smtg("val",True)=="This is a test"mocked.on("test_smtg","error",kp1=False).raises(Exception("This is a test"))withpytest.raises(Exception):mocked.test_smtg("error",False)deftest_should_be_able_to_raise(self):expected=Exception("test")mocked_call=mocked.on("test_no_args_no_return").raises(expected)withpytest.raises(Exception):mocked.test_no_args_no_return()assertmocked_call.called()assertmocked_call.full_filled()deftest_should_be_chainable(self):expected=Exception("test")(mocked.on("test_no_args_no_return").raises(expected).on("test_smtg","error",kp1="ok").on("test_smtg","nother",kp1=True))withpytest.raises(Exception):mocked.test_no_args_no_return()mocked.test_smtg("error","ok")mocked.test_smtg("nother",kp1=True)classTestRepeatControl:deftest_should_limit_once(self):mocked_call=mocked.on("test_no_args_no_return").once()mocked.test_no_args_no_return()withpytest.raises(UnexpectedCall):mocked.test_no_args_no_return()assertmocked_call.called()assertmocked_call.full_filled()deftest_should_limit_twice(self):mocked_call=mocked.on("test_no_args_no_return").twice()mocked.test_no_args_no_return()mocked.test_no_args_no_return()withpytest.raises(UnexpectedCall):mocked.test_no_args_no_return()assertmocked_call.called()assertmocked_call.full_filled()deftest_should_limit_x_times(self):mocked_call=mocked.on("test_smtg","test").times(5)mocked.test_smtg("test")mocked.test_smtg("test")mocked.test_smtg("test")mocked.test_smtg("test")mocked.test_smtg("test")withpytest.raises(UnexpectedCall):mocked.test_smtg("test")assertmocked_call.called()assertmocked_call.full_filled()mocked.on("test_smtg","test").returns("ok")# Should pass to next definition after max time reachedassertmocked.test_smtg("test")=="ok"deftest_full_filled_should_raise_if_not_full(self):call=mocked.on("test_no_args_no_return").times(5)mocked.test_no_args_no_return()mocked.test_no_args_no_return()mocked.test_no_args_no_return()mocked.test_no_args_no_return()assertcall.full_filled()isFalsewithpytest.raises(NotFullFilled):mocked.assert_full_filled()mocked.reset()deftest_full_filled_should_be_ok_with_correct_x_times(self):call=mocked.on("test_no_args_no_return").times(4)mocked.test_no_args_no_return()mocked.test_no_args_no_return()mocked.test_no_args_no_return()mocked.test_no_args_no_return()assertcall.full_filled()isTruemocked.assert_full_filled()mocked.reset()classTestMissUsageExceptions:deftest_should_raise_on_unknown_method(self):withpytest.raises(UnexpectedMethod):mocked.test_no_args_no_return()deftest_should_raise_on_invalid_arguments(self):mocked_call=mocked.on("test_no_return","test",kp1="new")withpytest.raises(UnexpectedArguments):mocked.test_no_return("pasglop","new")assertmocked_call.called()isFalsewithpytest.raises(UnexpectedArguments):mocked.test_no_return("test","fun")assertmocked_call.called()isFalsedeftest_should_not_match_invalid_kwargs_as_args(self):mocked.on("test_smtg","test",kp1="test")withpytest.raises(UnexpectedArguments):mocked.test_smtg("test","testa")withpytest.raises(UnexpectedArguments):mocked.test_smtg("test",kp1="testa")deftest_should_not_match_invalid_args_as_kwargs(self):mocked.on("test_smtg","test",kp1="test")withpytest.raises(UnexpectedArguments):mocked.test_smtg(p1="testa",kp1="test")withpytest.raises(UnexpectedArguments):mocked.test_smtg("testa",kp1="test")classTestCallLinks:deftest_should_allowed_linked_calls(self):(mocked.on("test_no_args").returns("Sparta").before("test_no_args_no_return").before("test_smtg","a",kp1="b").returns("Twice").before("test_smtg","c").returns("Last"))assertmocked.test_no_args()=="Sparta"mocked.test_no_args_no_return()assertmocked.test_smtg("a","b")=="Twice"assertmocked.test_smtg("c")=="Last"deftest_should_not_allow_linked_call_if_ancestor_is_invalid(self):(mocked.on("test_no_args").returns("Sparta").before("test_no_args_no_return").before("test_smtg","a",kp1="b").returns("Twice").before("test_smtg","c").returns("Last"))withpytest.raises(UnexpectedCall):mocked.test_no_args_no_return()withpytest.raises(UnexpectedCall):mocked.test_smtg("a","b")withpytest.raises(UnexpectedCall):assertmocked.test_no_args()=="Sparta"mocked.test_smtg("a","b")withpytest.raises(UnexpectedCall):assertmocked.test_no_args()=="Sparta"mocked.test_smtg("c")withpytest.raises(UnexpectedCall):(mocked.on("test_no_args").returns("Sparta").before("test_no_args_no_return").before("test_smtg","a",kp1="b").returns("Twice").before("test_smtg","c").returns("Last"))assertmocked.test_no_args()=="Sparta"mocked.test_no_args_no_return()assertmocked.test_smtg("c")=="Last"classTestNonAbsoluteParameters:classTestInArgs:deftest_ANY_should_match_anything(self):mocked_call=mocked.on("test_smtg",Mock.ANY,kp1="test")mocked.test_smtg("test","test")mocked.test_smtg(124,"test")mocked.test_smtg({"blah":"test"},"test")mocked.test_smtg(True,"test")mocked.test_smtg({1,2,5},"test")assertmocked_call.called()assertmocked_call.full_filled()mocked.reset()deftest_AnyTyped_should_match_provided_types(self):mocked_call=mocked.on("test_smtg",Mock.AnyTyped((str,dict)),kp1="test")mocked.test_smtg("test","test")mocked.test_smtg({"blah":"test"},"test")withpytest.raises(UnexpectedArguments):mocked.test_smtg(124,"test")withpytest.raises(UnexpectedArguments):mocked.test_smtg(True,"test")withpytest.raises(UnexpectedArguments):mocked.test_smtg({1,2,5},"test")assertmocked_call.called()assertmocked_call.full_filled()mocked.reset()deftest_AnyStrMatching_should_match_any_str_matching_pattern(self):mocked_call=mocked.on("test_smtg",Mock.AnyStrMatching(r"[1-4]49[0-9].*"),kp1="test")mocked.test_smtg("3495abnug","test")mocked.test_smtg(3495,"test")withpytest.raises(UnexpectedArguments):mocked.test_smtg(124,"test")withpytest.raises(UnexpectedArguments):mocked.test_smtg(True,"test")withpytest.raises(UnexpectedArguments):mocked.test_smtg("a1490","test")withpytest.raises(UnexpectedArguments):mocked.test_smtg("1590","test")withpytest.raises(UnexpectedArguments):mocked.test_smtg({1,2,5},"test")assertmocked_call.called()assertmocked_call.full_filled()mocked.reset()classTestInKwArgs:deftest_ANY_should_match_anything(self):mocked_call=mocked.on("test_smtg","test",kp1=Mock.ANY)mocked.test_smtg("test","test")mocked.test_smtg("test",124)mocked.test_smtg("test",{"blah":"test"})mocked.test_smtg("test",True)mocked.test_smtg("test",{1,2,5})assertmocked_call.called()assertmocked_call.full_filled()mocked.reset()deftest_AnyTyped_should_match_provided_types(self):mocked_call=mocked.on("test_smtg","test",kp1=Mock.AnyTyped((str,dict)))mocked.test_smtg("test","test")mocked.test_smtg("test",{"blah":"test"})withpytest.raises(UnexpectedArguments):mocked.test_smtg("test",124)withpytest.raises(UnexpectedArguments):mocked.test_smtg("test",True)withpytest.raises(UnexpectedArguments):mocked.test_smtg("test",{1,2,5})assertmocked_call.called()assertmocked_call.full_filled()mocked.reset()deftest_AnyStrMatching_should_match_any_str_matching_pattern(self):mocked_call=mocked.on("test_smtg","test",kp1=Mock.AnyStrMatching(r"[1-4]49[0-9].*"))mocked.test_smtg("test","3495abnug")mocked.test_smtg("test",3495)withpytest.raises(UnexpectedArguments):mocked.test_smtg("test",124)withpytest.raises(UnexpectedArguments):mocked.test_smtg("test",True)withpytest.raises(UnexpectedArguments):mocked.test_smtg("test","a1490")withpytest.raises(UnexpectedArguments):mocked.test_smtg("test","1590")withpytest.raises(UnexpectedArguments):mocked.test_smtg("test",{1,2,5})assertmocked_call.called()assertmocked_call.full_filled()mocked.reset()classTestSpecialCases:deftest_should_match_kwargs_passed_as_args(self):mocked_call=mocked.on("test_smtg","test",kp1="test")mocked.test_smtg("test","test")assertmocked_call.called()assertmocked_call.full_filled()mocked.reset()mocked_call=mocked.on("test_smtg","test","test")withpytest.raises(UnexpectedArguments):mocked.test_smtg("test","test")mocked.reset()mocked_call=mocked.on("test_smtg",p1="test",kp1="test")withpytest.raises(UnexpectedArguments):mocked.test_smtg("test","test")mocked.reset()deftest_should_match_args_passed_as_kwargs(self):mocked_call=mocked.on("test_smtg","test",kp1="test")mocked.test_smtg(p1="test",kp1="test")assertmocked_call.called()assertmocked_call.full_filled()