Package: pymox
Python mocking on steroids.DocsCI/CDPackageMetaWhatPymox is mocking on steroids. It's a powerful mock object framework for Python, providing many
tools to help with your unit tests, so you can write them in an easy, quick and intuitive way.WhyWhyPymox? Python already has batteries included. It has its ownmocklibrary, widely used in
Python applications.So, whypytest, given we have Python'sunittest?
Whyarroworpendulumgiven we have Python'sdatetime?
WhyX, given we have Python'sY?You got it 😉!Coming SoonAsync supportDecoratorIgnore argsString importsHowInstallpipinstallpymoxCool StuffNew Elegant Way# conftest.pypytest_plugins=("mox.testing.pytest_mox",)# test.pyfrommoximportexpect,stuboutclassTestOs:deftest_getcwd(self):withstubout(os,'getcwd')asm_getcwd,expect:m_getcwd.to_be.called_with().and_return('/mox/path')assertos.getcwd()=='/mox/path'mox.verify(m_getcwd)If you want to be less verbose:classTestOs:deftest_getcwd(self):withstubout(os,'getcwd')asm_getcwd:m_getcwd().returns('/mox/path')assertos.getcwd()=='/mox/path'mox.verify(m_getcwd)Anything you put inside the context manager is a call expectation, so to not expect any call you can:classTestOs:deftest_getcwd(self):withstubout(os,'getcwd')asm_getcwd:pass# will raise a UnexpectedMethodCallErrorassertos.getcwd()=='/mox/path'mox.verify(m_getcwd)Dict AccessclassTestDict:deftest_dict_access(self):config={'env':'dev','reload':True}# doing in another way using create, but you can do with stubout toomock_config=mox.create(config)mock_config['env'].returns('prod')mock_config['reload'].returns(False)mox.replay(mock_config)assertmock_config['env']=='prod'assertmock_config['reload']isFalsemox.verify(mock_config)ComparatorsclassClient:defget(self,url,params):returnrequests.get(url,params)classService:defget_contacts(self):url='https://my.reallylong.service/api/v1/contacts/'params={'added':'7days','order_by':'-created'}returnClient().get(url,params)classTestSevice:deftest_get_contacts_comparators_str_and_key_value(self):withstubout(Client,'get')asm_get:url=mox.str_contains('/api/v1/contacts')params=mox.contains_key_value('added','7days')m_get(url,params).returns({})service=Service()assertservice.get_contacts()=={}mox.verify(m_get)deftest_get_contacts_comparators_and_func_in_is_a(self):withstubout(Client,'get')asm_get:url=mox.func(lambdav:str.startswith(v,'https://my.reallylong.service/'))params=mox.and_(mox.is_a(dict),mox.in_('added'),)m_get(url,params).returns({})service=Service()assertservice.get_contacts()=={}mox.verify(m_get)deftest_get_contacts_comparators_ignore_arg_not(self):withstubout(Client,'get')asm_get:url=mox.ignore_arg()params=mox.not_(mox.is_(None))m_get(url,params).returns({})service=Service()assertservice.get_contacts()=={}mox.verify(m_get)Other comparators:contains_attribute_value,in_,is_,is_almost,or_,same_elements_as,regexAnd RaisesclassTestOs:deftest_getcwd(self):withstubout(os,'getcwd')asm_getcwd:# .and_raise(..) also worksos.getcwd().raises(Exception('error'))withpytest.raises(Exception,match='error'):os.getcwd()mox.verify(m_getcwd)Multiple TimesclassTestOs:deftest_getcwd(self):withstubout(os,'getcwd')asm_getcwd:m_getcwd().returns('/mox/path')# the second call will return a different valuem_getcwd().returns('/mox/another/path')# the three subsequent calls will return "/"# if no argument is passed, multiple_times doesn't limit the number of callsm_getcwd().multiple_times(3).returns('/')assertos.getcwd()=='/mox/path'assertos.getcwd()=='/mox/another/path'mox.verify(m_getcwd)Any orderIf you stub out multiple, the order os calls is enforced, unless you useany_orderclassTestOs:deftest_getcwd(self):withstubout.many([os,'getcwd'],[os,'cpu_count'])as(m_getcwd,m_cpu_count):m_getcwd().returns('/mox/path')m_cpu_count().returns('10')# will raise a UnexpectedMethodCallErrorassertos.cpu_count()=='10'assertos.getcwd()=='/mox/path'mox.verify(m_getcwd,m_cpu_count)deftest_getcwd_anyorder(self):withstubout.many([os,'getcwd'],[os,'cpu_count'])as(m_getcwd,m_cpu_count):m_getcwd().any_order().returns('/mox/path')m_cpu_count().any_order().returns('10')assertos.cpu_count()=='10'assertos.getcwd()=='/mox/path'mox.verify(m_getcwd,m_cpu_count)Remember/ValueThe Remember and Value are comparators, but they deserve their own section. They can be useful to
retrieve some values from deeper levels of your codebase, and bring to the test for comparison.
Let's see an example:classHandler:defmodify(self,d):# any integer key less than 5 is removed from the dictkeys_to_remove=[keyforkeyindifisinstance(key,int)andkey<5]forkeyinkeys_to_remove:deld[key]returnddefsend(self,d):returndclassManager:def__init__(self,handlers=None):self.handlers=handlersor[]defprocess(self,d):forhandlerinself.handlers:modified=handler.modify(d)handler.send(modified)classTestManager(mox.MoxTestBase):deftest_manager_process(self):mydict={1:"apple",4:"banana",6:{2:3,4:{1:"orange",7:8}},8:3}myvalue=mox.value()withmox.stubout(Handler,'send')asmock_send:# so we use remember in the send call, and its value then the function is# called will go to `myvalue`mock_send(mox.remember(myvalue))Manager([Handler()]).process(mydict)mox.verify(mock_send)# now we can compare myvalue with what we think its value must beassertmyvalue=={6:{2:3,4:{1:'orange',7:8}},8:3}Classic WayimportmoximportosclassTestOs:deftest_getcwd(self):m=mox.Mox()m.stubout(os,'getcwd')# callsos.getcwd().returns('/mox/path')m.replay_all()assertos.getcwd()=='/mox/path'm.verify_all()if__name__=='__main__':importunittestunittest.main()Jurassic WayimportmoximportosclassTestOs(mox.MoxTestBase):deftest_getcwd(self):self.mox.StubOutWithMock(os,'getcwd')# callsos.getcwd().AndReturn('/mox/path')self.mox.ReplayAll()self.assertEqual(os.getcwd(),'/mox/path')self.mox.VerifyAll()if__name__=='__main__':importunittestunittest.main()Project LinksStack OverflowPyPIGitHubDocumentationChangelog