Package: pyhamcrest-toolbox
PyHamcrest ToolboxPyHamcrest is a great thing for writing reusable tests, but sometimes
writing your own matchers may be a bit of a chore. This project aims at
making this task easy, fast and even fun (for want of a better word).To reiterate the obvious, a test should always do all the checks it has to do,
and even if some of them fail, the rest should still be run. That way you will
get a better idea of what’s gone wrong with your code, and you will waste
less time fixing the first of the checks only to find the the second one is
still failing, and that means that you should have fixed the first one in a
different way.So, instead of this:deftest_the_holy_grail():the_holy_grail=seek_the_holy_grail()assert_that(the_holy_grail.is_holy(),is_(True))assert_that(the_holy_grail.depth,greater_than(5))assert_that(the_holy_grail.width,greater_than(6))assert_that(the_holy_grail.height,greater_than(7))this should be written as:deftest_the_holy_grail():the_holy_grail=seek_the_holy_grail()assert_that(the_holy_grail,is_holy().with_depth(greater_than(5)).with_width(greater_than(6)).with_height(greater_than(7)))or:deftest_the_holy_grail():the_holy_grail=seek_the_holy_grail()assert_that(the_holy_grail,grail(holy=True,width=5))Both these examples, however, require writing your own matchers. With this toolbox,
it is easy.MulticomponentMatcherTheMulticomponentMatcherallows writing the chain-style matchers.All you have to do is to write youris_holymatcher that inherits from theMulticomponentMatcheras the backbone. Then you write individual matchers
for each of the holy grail properties enhancing them with theMatcherPlugin, and you register them with theis_holymatcher.So, this is youris_holymatcher:classIsHolyMatcher(MulticomponentMatcher):def__init__(self):super().__init__()defis_holy():returnIsHolyMatcher()And that’s it. You don’t have to override the usual matcher methods. Everything
will be done by the parent class. However, it doesn’t do any matching yet, so we
need to write the plugins. Let’s start with the actual holiness:classHolinessMatcher(MatcherPlugin):def__init__(is_holy=True):super().__init__()self.is_holy=is_holydefcomponent_matches(self,item):# the item will be a grailreturnself.is_holy==item.is_holy()defdescribe_to(self,description):description.append_text("A grail which is{}holy".format(""ifself.is_holyelse"not "))defdescribe_component_mismatch(self,item,mismatch_description):mismatch_description.append_text("The grail was{}holy".format(""ifitem.is_holy()else"not "))And then you register it with the main matcher:classIsHolyMatcher(MulticomponentMatcher):def__init__(self,is_holy):super().__init__()self.register(HolynessMatcher(is_holy))defholy(is_holy):returnIsHolyMatcher(is_holy)Of course, you could write thatHolinessMatcherlogic in yourIsHolyMatcher, but if we have the power of plugins, then why not use it?For now, we only have this bit:assert_that(the_grail,is_holy()), and
not the.with_width(...)stuff. So let’s write it. I won’t go through the
process of writing the plugin for the width as it is rather straightforward,
but here’s how you register it with the main matcher:classIsHolyMatcher(MulticomponentMatcher):def__init__(self,is_holy):super().__init__()self.register(HolinessMatcher(is_holy))defwith_width(self,value):returnself.register(GrailWidthMatcher(value))defholy(is_holy):returnIsHolyMatcher(is_holy)Now you can do theis_holy().with_width(greater_than(5))stuff.Note that you have to returnself.register(...)from the plugin registering methods,
as (a) you might want to chain them, and (b) the result of the chain still
needs to be a matcher.KwargMulticomponentMatcherThis matcher allows writing the kwarg-style matchers (as in the second example
above), which are more pythonic, but look kind of unnatural when you want to
match against another matcher instead of a plain value. I will show what I mean
in a minute.The general approach is the same as with the multicomponent matcher: you write
matcher plugins for your components, and then you register them with your main
matcher:classGrailMatcher(KwargMulticomponentMatcher):def__init__(self,holy=None,width=None):self.register_for_kwarg(HolinessMatcher(holy),holy)self.register_for_kwarg(GrailWidthMatcher(width),width)And then in your tests you do:deftest_correct_width_wrong_holiness(self,my_grail):assert_that(my_grail,grail(holy=True,width=4))As I said before, this looks more pythonic, however, if you want to check your
values against matchers, and not just plain values (likewidth=4here), your
code starts looking a bit strange:deftest_correct_width_wrong_holiness(self,my_grail):assert_that(my_grail,grail(holy=True,width=greater_than(4)))My recommendation is to use the chain-style matchers if you know that your
main matcher might be used this way.DemosYou can find the demos for both approaches in thedemofolder of this repo.
Clone it, install the requirements fromdemo/requirements.txt,  and runpytest demo/test_*