Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

updated documentation for 0.5, added sphinx doc

  • Loading branch information...
commit 524e210fb17e38b5faf70c7e6e025c29d56874fa 1 parent aa0f3e7
@gfxmonk authored
View
3  .gitignore
@@ -4,3 +4,6 @@ dist
.coverage
*.egg-info
*.tmproj
+doc/build
+.*
+0inst
View
37 LICENCE
@@ -1,30 +1,15 @@
-BSD Licence:
+Copyright (C) 2010 Tim Cuthbertson
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
- * Redistributions in binary form must reproduce the above
- copyright notice, this list of conditions and the following
- disclaimer in the documentation and/or other materials provided
- with the distribution.
-
- * Neither the name of Michael Foord nor the name of Voidspace
- may be used to endorse or promote products derived from this
- software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
View
92 Makefile
@@ -0,0 +1,92 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+PAPER =
+BUILDDIR = doc/build
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest copy doc
+
+help:
+ @echo "Please use \`make <target>' where <target> is one of"
+ @echo " html to make standalone HTML files"
+ @echo " dirhtml to make HTML files named index.html in directories"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " qthelp to make HTML files and a qthelp project"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
+
+doc: html copy
+copy:
+ rsync -avz doc/build/html/ ~/Sites/gfxmonk/dist/doc/mocktest/
+clean:
+ -rm -rf $(BUILDDIR)/*
+
+html:
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+pickle:
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
+
+json:
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+ @echo
+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/mocktest.qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/mocktest.qhc"
+
+latex:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+ @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
+ "run these through (pdf)latex."
+
+changes:
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+ @echo
+ @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILDDIR)/doctest/output.txt."
View
507 README.markdown
@@ -1,507 +0,0 @@
-how about:
-
-# heirarchial mocking:
-when(obj).meth().then_return(foo)
-
-when(obj).meth.with(x,y,z).then_return(foo)
-
-replace(obj).property.with(some_stub)
-
-expect(obj).meth.with(fdsf).once()
-
-# direct mocking:
-# how about we just reserve mock_* and with_* for mocktest?
-# when() and expect() can still access the mock puppet, but nobody else?
-# mock_calls,
-x = mock("foo")
-x = mock("foo").with(x=1,y=2)
-x = mock("foo").with_children(x=1,y=2)
-x = mock("foo").with_methods(x=1,y=2)
-x = mock("foo").with_spec(bob)
-
-when(x).meth.then_return(fdjsafj)
-expect(x).meth.once
-
-x.mock_calls.should == [1,2,3]
-s.mock_return = ___
-s.mock_freeze()
-s.mock_unfreeze()
-
-when(obj)().then_return(foo) # possible with the below as well? probably not...
-when(obj).__call__.then_return(result)
-
-
-raw mocks go away... a mock() call will yield a combination raw mock that has with_* and mock_* methods, and can be puppeted via when(), expect(), replace(), etc...
-
-
-
-
-##
-
-
-#test inheritance
-
-
-inherit setup/teardown (but no test methods) from another class:
-
-class MyTest(TestCase.skeleton): pass
-
-class MyTest(skeleton(TestCase)): pass
-
-
-
-
-
-## mocktest (version 0.3)
-... is a powerful and easy-to-use mocking library, inspired by rspec and
-similar in many ways to Michael Foord's popular Mock module.
-
-The main features are:
-
- - powerful expectation matching behaviour for mock objects
- - automatic verification of expectations (before each test's tearDown)
- - automatic rollback of inserted mock objects after each test
-
-It's released under the BSD licence (see the LICENCE file).
-
-Source / Readme / Issues:
-[http://github.com/gfxmonk/mocktest/tree/master](http://github.com/gfxmonk/mocktest/tree/master)
-
-Cheese shop entry:
-[http://pypi.python.org/pypi/mocktest](http://pypi.python.org/pypi/mocktest/0.1)
-
-### Important notes for this version
-
-In mocktest 0.3, a few key objects and attributes have been renamed:
-
- - the `mock_wrapper()` function is now just called `mock()`
- - to get a raw mock from a mock wrapper, you now use `.raw` instead of `.mock`
-
-To sum up, where you would previously use `mock_wrapper().mock` you now would
-use `mock().raw`. The new names are more concise and less confusing to new users.
-
-### Where did it come from?
-I am a big fan of rspec, but less of a fan of ruby as a whole.
-I wanted a to use rspec's powerful `should_receive()` and associated matchers
-with my python mock objects.
-
-mocktest is by no means a port of rspec - it is smaller and simpler, and a lot
-more pythonic.
-
-### what *are* mocks?
-
-What are mocks used for? mocks can pretend to be pretty much any object. Mocks
-record what happens to them (accessors, method calls, etc) and allow you to
-verify that this is what you expected. Replace a database connection with a mock
-and make sure the right commands are being sent to your database - without the
-overhead and trouble of using an actual database. Replace os.system with a mock,
-and supply your own response to shell commands. Mocks can be used to satisfy
-dependencies or simulate external conditions in your program so you can focus
-on unit-level testing.
-
----
-# mocktest
-
-## TestCase
-
-When using mocktest, you should always use `mocktest.TestCase` instead of
-`unittest.TestCase`. Mocktest's version is almost identical, but automatically
-calls the required setup and teardown hooks for the mocktest library.
-
-There is one important addition to the `mocktest.TestCase` class:
-
- * `assertRaises(exception_type, callable, < ... >)`
-
-Where additional args can be:
-
- * `args=(arg1,arg2)`
- * Fails unless the arguments provided to the exception constructor match the
- given args
- * `message="some string"`
- * Fails unless the error message (i.e. str(exception)) is equal to this
- message
- * `matches="string"` or `matches=re.compile("match_string", custom_flags)`
- * Just like message, except a regex.search is required to return a match
- instead of requiring the strings to be identical
-
-This was adapted from [http://code.activestate.com/recipes/307970/](http://code.activestate.com/recipes/307970/)
-
----
-## mocks
-
-mocktest is still a young framework, and is likely to evolve. While the
-inspiration is from rspec, a lot of the mechanics differ either necessarily
-because of differences between ruby and python, and a bunch of things were done
-differently to make things cleaner.
-
-mocks in mocktest have 3 components. This might sound like two more than you
-would need, but bear with me:
-
- * **raw mock**: a raw mock is a minimal mock object that can be put wherever
- you need it. It records interaction that happens to it, but it can't
- distinguish between interaction as part of your test and interaction caused
- by the rest of the program.
-
- Some mock frameworks only have this object, but adding methods for
- controlling and inspecting a mock interferes with the actual mock behaviour
- itself. For this reason, mocktest has a puppet-master of sorts:
-
- * **mock wrapper**: a mock wrapper is how you talk to a raw mock
- behind-the-scenes. It allows you to set the return value for a method,
- or see how many times and with what arguments a raw mock has been called.
- In your unit test source code, you will mostly be interacting with mock
- wrappers to define behaviour and specify your expectations.
-
- * **mock anchor**: finally, there is one piece missing in the puzzle. You
- have a mock, and you talk to it through a mock wrapper. But how do you
- insert it where it needs to go? A mock anchor latches on to a root
- object, and records every mock you create on it. Creating a mock called
- "some\_method" on a mock anchor will attach it to parent.some\_method.
- And more importantly, when your unit test is done it will revert
- parent.some_method to what it was before.
-
- By using a mock anchor, you ensure that your mocks never live past the
- lifespan of your test case - this can cause havok in other mock
- frameworks.
-
-So, how do you use all of these things?
-
- anchor = mock_on(some_object)
-
-creates a **mock anchor** attached to some\_object.
-
- wrapper = anchor.foo
-
-creates a **raw mock** called `foo`, and attaches it to some\_object.foo. The
-value of this expression is a **mock wrapper** linked to the newly-created
-some_object.foo raw mock.
-
-If your mock is not attached to anything, you can create a standalone mock
-wrapper:
-
- wrapper = mock()
-
-you can get the raw mock (to provide to your code) with:
-
- raw_mock = wrapper.raw
-
-and if you just have a raw mock object, you can get a wrapper for it by calling:
-
- wrapper = mock(raw_mock)
-
-This might seem a little confusing, but hopefully the examples below will help.
-
----
-### Mock customisation
-
-A mock has a few options for specifying its behaviour:
-
- wrapper = mock()
- wrapper.name = "my mock"
- wrapper.return_value = "result!"
-
-which will result in:
-
- >>> raw_mock = wrapper.raw
- >>> str(raw_mock)
- 'my mock'
-
- >>> raw_mock()
- 'result!'
-
-The other property your mock wrapper has is:
-
- def my_action(*args):
- print "called with: %r" % (args,)
- return "result..."
- wrapper.action = my_action
-
-resulting in:
-
- >>> wrapper.raw('a','b','c')
- called with: ['a','b','c']
- 'result...'
-
-*note*: You cannot use both action and return_value on the same mock, you'll get
-a `MockError`. If you want to have an action and particular return value, return the
-value from the action.
-
-Because setting properties like this is a little verbose, mock wrapper objects
-provide some helpful methods. These methods all return the mock wrapper itself,
-so you can chain them together.
-
- mock().named('my mock')
- mock().returning(10)
- mock().with_action(lambda x: x + 1)
-
-In addition, there are some additional methods which don't directly
-relate to attributes:
-
- mock().raising(IOError)
- mock().raising(IOError('permission denied'))
-
-`raising` takes an exception class or instance and raises it when the mock is
-called. This overwrites the mock's action attribute (and will fail if you have
-already set an action).
-
-
-By default, calling `raw_mock.some_attribute` will force `some_attribute` to be
-added to the mock. If you don't want this behaviour, you can lock down the mock
-using:
-
- mock().frozen()
-
-This will raise an `AttributeError` when any new attribute is accessed (or set)
-on the mock.
-
- mock().with_children('x', 'y', z='zed')
- mock().with_methods('x','y', z='zed')
- mock().with_spec(some_object)
-
-Children and methods are similar. They take in any number of string arguments
-and keyword arguments. String arguments ensure that an attribute of that name
-exists on the mock. Keyword arguments specify its value, as well.
-
-The difference between `methods` and `children` is that the value of a method
-is used for a child's return_value:
-
- >>> wrapper = mock().with_methods('y', z='zed')
- >>> wrapper.raw.z()
- 'zed'
-
-whereas child values are used as-is:
-
- >>> wrapper = mock().with_children('y', z='zed')
- >>> wrapper.raw.z
- 'zed'
-
-If you have an object that you want to mimic, you can use:
-
- mock().with_spec(some_object)
-
-If `some_object` has attributes "foo" and "bar", so too will your mock. The
-values for these attributes are mocks; they do not copy the `spec_object`'s
-attribute values.
-
-Calling `with_methods`, `with_children` or `with_spec` has the side effect of
-freezing the mock. Any attributes that aren't already on the mock cannot be
-added. If you want to control this yourself, use `wrapper.frozen()` and
-`wrapper.unfrozen()`
-
-
-If you want to, you can also override special methods on a mock:
-
- >>> wrapper = mock().with_children( __len__ = lambda x: 5 )
- >>> len(wrapper.raw)
- 5
-
-There's a bit of black magic to special method overriding, so please send bug
-reports if you find something that doesn't work.
-
----
-### Expectations
-
-Having earlier said that mocktest is not rspec, here are a bunch of useful
-examples ported from the
-[rspec documentation](http://rspec.info/documentation/mocks/message_expectations.html)
-
-The basic setup of a test case is identical to using unittest.TestCase:
-
- from mocktest import *
-
- class MyTestClass(TestCase):
- def setUp(self):
- # common setup actions...
-
- def tearDown(self):
- # common teardown actions...
-
- def test_feature_a(self):
- #test the functionality in feature a
-
- def test_feature_b(self):
- #test the functionality in feature b
-
-#### Expecting calls
-
- mock_os = mock_on(os)
- mock_os.system.is_expected
-
-This will fail your test unless os.system() is called at least once during
-the current test case (the check is made right before the `tearDown()` method
-is executed)
-
-If you don't want an anchored mock, you can use:
-
- wrapper = mock()
- raw_mock = wrapper.raw
- wrapper.is_expected
-
-You can then pass raw_mock into a function and ensure that it is called. But
-you should **not** set `os.system = raw_mock`. This will change `os.system`
-for the life of your tests, and will almost certainly mess up the rest of your
-test cases. That is why the `mock_on()` function exists to automatically
-clean up your mocks.
-
-#### Multiplicites of calls
-
-The default `is_expected` ensures that your method is called at least once.
-There are other options:
-
- mock_anchor.method.is_expected.no_times() # shouldn't be called
- mock_anchor.method.is_expected.once() # once (and no more)
- mock_anchor.method.is_expected.twice()
- mock_anchor.method.is_expected.thrice() # (isn't thrice a great word?)
-
- mock_anchor.method.is_expected.exactly(4).times
- mock_anchor.method.is_expected.at_least(10).times
- mock_anchor.method.is_expected.at_most(2).times
-
-this also works just fine:
-
- mock_anchor.method.is_expected.at_most(2)
-
-("times" is unnecessary, but it helps for readability)
-
-#### Expecting Arguments
-
- mock_anchor.method.is_expected.with(<args>)
-
-e.g:
-
- mock_anchor.method.is_expected.with_args(1, 2, 3)
- mock_anchor.method.is_expected.with_args(1, 2, 3, foo='bar').once()
- mock_anchor.method.is_expected.with_args() # No arguments allowed
-
-*Note:* When adding conditions to a call, the multiplicity (number of
-calls) is checked _after_ the other conditions.
-This means that while the following will fail:
-
- mock_anchor.method.is_expected.once()
- myobj.action('a')
- myobj.action('b')
-
-this will succeed:
-
- mock_anchor.method.is_expected.once().with('a')
- myobj.action('a')
- myobj.action('b')
-
-This is the same way that rspec works, and it is the most flexible,
-since you can always put a bound on the total invocations by adding a
-non-conditional multiplicity check:
-
- mock_anchor.method.is_expected.twice()
-
-(you can apply as many `is_expected`'s to a single mock as you like)
-
-#### Argument Constraints
-
-When you don't know the exact arguments, you can supply a checking function.
-If this function does not return True, the expectation fails:
-
- mock_anchor.method.is_expected.where_args(lambda arg1, arg2: arg1 == arg2)
- mock_anchor.method.is_expected.where_args(lambda arg: isinstance(arg, dict))
-
-It doesn't have to be an inline lambda expression:
-
- def check_args(*args, **kwargs):
- if len(args) > 3:
- return False
- if 'bad_argument' in kwargs:
- return False
- return True
-
- mock_anchor.method.is_expected.where_args(check_args)
-
-#### Proxying
-
-Proxying options are confusingly similar to argument constraints. But where argument
-constraints validate the arguments that the mock is called with, proxying controls
-weather the mock intercepts a method call in the first place. An example:
-
- class MyObject(object):
- def do_something(self, some_number):
- return some_number + 10
- obj = MyObject()
-
- # (note the use of `obj` to match the implicit `self` paramater)
- wrapper = mock_on(obj).do_something.with_args(obj, 5).returning(500)
- mock_do_something = wrapper.raw
-
- # now if the arguments we give to the mock don't match what we supplied to
- # with_args, the call will go ahead just as if we hadn't set a mock on obj:
-
- assert mock_do_something(1) == 11
- assert mock_do_something(2) == 12
-
- # but if the arguments do match:
- assert mock_do_something(5) == 500
-
- # note that only the intercepted call is recorded
- assert wrapper.called.once()
-
-Just like argument constraints, you can also use `where_args` - e.g:
-
- def second_arg_is_a_string(a, b):
- return isinstance(b, str)
-
- wrapper = mock_on(obj).do_something.where_args(second_arg_is_a_string).returning("STRING")
- mock_do_something = wrapper.raw
-
- assert mock_do_something(1) == 11
- assert mock_do_something(2) == 12
-
- # but if the arguments do match:
- assert mock_do_something('5') == "STRING"
-
-
-#### Post-checking
-Specifying your expectations before anything happens is sometimes not the best
-(or easiest) thing to do.
-
-It's possible to just inspect the state of a mock to see what's happened to it
-so far. `called` is almost identical to `is_expected`. Unlike an expectation
-object, The result of a `called` expression should be compared to `True` or
-`False` to check whether the expressed call(s) did indeed happen.
-
- self.assertTrue(mock().called.once().with_args('foo'))
- if not mock().called.once():
- assert False, "Things went bad!"
-
-But the most useful feature of of `called` is its ability to retrieve the calls
-that the mock has received. So in the following example:
-
- wrapper = mock()
- raw_mock = wrapper.raw
-
- raw_mock('a', b='foo')
- raw_mock('b')
- raw_mock(b='bar')
-
- >>> wrapper.called.thrice().get_calls()
- [(('a',), {'b': 'foo'}), ('b',), (None, {'b': 'bar'})]
-
-Note that where a call has no arguments or has no keyword-arguments, the
-first or second element (respectively) in the call tuple is None instead of an
-empty tuple or dict. This is mostly for readability, because there are already
-enough parentheses in the mix.
-
-**Note**: get_calls will fail if the assertions made after `called` are not met.
-e.g: if mock has been called once and you ask for
-`wrapper.called.twice().get_calls()`, then you'll get an AssertionError.
-
-If you're only expecting one call, you can use `get_args`:
-
- mock(b='bar')
- >>> wrapper.called.once().get_args()
-
-**Note** that `get_args` requires you to explicitly specify `once()`.
-
----
-# Testing the mocktest library
-I use [nosetests](http://code.google.com/p/python-nose/), and just run it from
-the root directory. You probably should too!
-
-#Thanks
-[Michael Foord](http://www.voidspace.org.uk/python/mock.html)
View
1  VERSION
@@ -0,0 +1 @@
+0.5
View
196 conf.py
@@ -0,0 +1,196 @@
+# -*- coding: utf-8 -*-
+#
+# mocktest documentation build configuration file, created by
+# sphinx-quickstart on Tue Dec 28 12:04:30 2010.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.append(os.path.abspath('.'))
+
+# -- General configuration -----------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.ifconfig']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['doc/templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8'
+
+# The master toctree document.
+master_doc = 'doc/index'
+
+# General information about the project.
+project = u'mocktest'
+copyright = u'2010, Tim Cuthbertson'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '0.5'
+# The full version, including alpha/beta/rc tags.
+release = '0.5'
+
+autodoc_default_flags = ['members']
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of documents that shouldn't be included in the build.
+#unused_docs = []
+
+# List of directories, relative to source directory, that shouldn't be searched
+# for source files.
+exclude_trees = ['doc/build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. Major themes that come with
+# Sphinx are currently 'default' and 'sphinxdoc'.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['doc/static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_use_modindex = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = ''
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'mocktestdoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+ ('index', 'mocktest.tex', u'mocktest Documentation',
+ u'Tim Cuthbertson', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_use_modindex = True
View
238 doc/index.rst
@@ -0,0 +1,238 @@
+mocktest
+********
+... is a powerful and easy-to-use mocking library, inspired by rspec and
+similar in some ways to Michael Foord's popular Mock module.
+
+The main features are:
+
+ - expressive DSL for specifying expectations, stubs and replacement objects
+ - powerful expectation matching behaviour for mock objects
+ - automatic verification of expectations (before each test's tearDown)
+ - automatic rollback of inserted mock objects after each test
+ - descriptive error messages when something fails
+
+
+It's released under the BSD licence (see the LICENCE file).
+
+| Source / Readme / Issues:
+| http://github.com/gfxmonk/mocktest/tree/master
+
+
+| Cheese shop entry:
+| http://pypi.python.org/pypi/mocktest
+
+
+| Zero install feed:
+| http://gfxmonk.net/dist/0install/mocktest.xml
+
+API reference
+-----------------
+
+This document is an introduction, if you are looking for an API reference
+you should read the :mod:`mocktest module documentation <mocktest>`.
+
+
+Important notes for this version
+--------------------------------
+
+Mocktest 0.5 is a fairly thorough rewrite. Most of the semantics and features
+remain the same, but the implementation has been simplified. Some features are
+no longer present, other (hopefully more useful) features have been added. I
+apologise for those using mocktest <=0.3 - most of the features you use should
+still work, they may just be written differently.
+
+Most importantly, there is no longer any distinction between a raw mock and a
+mock wrapper. Instead, mock expectations are specified by using one of the
+global functions - `expect` and `when`. The distinction turned out to be
+confusing, and made many tests awkward.
+
+Where did it come from?
+-----------------------
+I am a big fan of rspec, but less of a fan of ruby as a whole.
+I wanted a to use rspec's powerful `should_receive()` and associated matchers
+with my python mock objects.
+
+mocktest is by no means a port of rspec - it is smaller and simpler, and a lot
+more pythonic.
+
+what *are* mocks?
+-----------------
+
+What are mocks used for? mocks can pretend to be pretty much any object. Mocks
+record what happens to them (accessors, method calls, etc) and allow you to
+verify that this is what you expected. Replace a database connection with a mock
+and make sure the right commands are being sent to your database - without the
+overhead and trouble of using an actual database. Replace os.system with a mock,
+and supply your own response to shell commands. Mocks can be used to satisfy
+dependencies or simulate external conditions in your program so you can focus
+on unit-level testing.
+
+mocktest usage
+**************
+
+When using mocktest, you should always use :class:`mocktest.TestCase` instead of
+:class:`unittest.TestCase`. Mocktest's version is almost identical, but automatically
+calls the required setup and teardown hooks for the mocktest library.
+
+If you wish to use mocks outside test cases, you can use the mock transaction
+manager directly to handle these checks for you.
+
+ >>> from mocktest import MockTransaction
+ >>> with MockTransaction:
+ ... # perform all your checks in here
+ ... # expectations will be verified once the indented block finishes
+
+Or, if you are playing around on the console, you can manually call
+`MockTransaction.__enter__()` and `MockTransaction.__exit()` to start/end a
+mock transaction.
+
+There is one important addition to the :class:`~mocktest.mocktest.TestCase` class:
+
+.. automethod:: mocktest.mocktest.TestCase.assertRaises
+
+This was adapted from http://code.activestate.com/recipes/307970/
+
+Creating mocks
+--------------
+
+mocktest is still a young framework, and is likely to evolve. While the
+inspiration is from rspec, a lot of the mechanics differ either necessarily
+because of differences between ruby and python, or just to make things cleaner.
+
+One important part of mocking is test isolation - that is, changes you make in
+one test for the sake of mocking should never be visible outside that test
+case. Mocktest takes care of all that for you, even when you mock or replace
+attributes on global objects.
+
+So, let's get started:
+
+If you want to replace a method on an existing object, you can use :func:`~mocktest.mocking.when`:
+
+ >>> when(some_object).method.then_return(True)
+
+This will ensure that `some_object.method()` always returns True (and doesn't call
+the previous implementation of `method`, if there is one). This action will
+take place regardless of the arguments passed in to `method`.
+
+To only deal with some of the calls made to method, you can specify under which
+conditions your action should occur by just passing those arguments when call the
+:func:`~mocktest.mocking.when` function's `method`. For example:
+
+ >>> when(some_object).method().then_return('no args')
+ >>> when(some_object).method(1, 2, 3).then_return('one two three')
+
+After this, you would see:
+
+ >>> some_object.method()
+ 'no args'
+
+ >>> some_object.method(1, 2, 3)
+ 'one two three'
+
+ >>> some_object.method('unexpected arguments')
+ TypeError: stubbed method 'method' received unexpected arguments: ('unexpected arguments')
+ Allowable argument conditions are:
+ - arguments equal to: ()
+ - arguments equal to: (1, 2, 3)
+
+In order to make sure that the method call you want to happen actually does, you
+can use :func:`~mocktest.mocking.expect`. :func:`~mocktest.mocking.expect`
+is exactly like :func:`~mocktest.mocking.when`, except once the test is complete,
+it makes sure the method you were expecting really was called.
+
+And finally, if you don't already have an object, you can quickly get one by calling
+:func:`~mocktest.mocking.mock`:
+
+ >>> obj = mock('my mock')
+
+Mock customisation
+------------------
+
+A stubbed method has a number of options for specifying its behaviour including
+return values and expectations. For the full API, see :ref:`setting-expectations`.
+
+The basic setup of a test case is identical to using unittest.TestCase:
+
+ >>> from mocktest import *
+ >>> class MyTestClass(TestCase):
+ ... def setUp(self):
+ ... # common setup actions...
+ ...
+ ... def tearDown(self):
+ ... # common teardown actions...
+ ...
+ ... def test_feature_a(self):
+ ... #test the functionality in feature a
+ ...
+ ... def test_feature_b(self):
+ ... #test the functionality in feature b
+
+Expecting calls
+^^^^^^^^^^^^^^^
+
+ >>> expect(os).system
+
+This will fail your test unless os.system() is called at least once during
+the current test case (the check is made right before the `tearDown()` method
+is executed).
+
+Expecting Arguments
+^^^^^^^^^^^^^^^^^^^
+
+| To specify what argument's you're expecting, just pass them in:
+| `expect(obj).method(<args>)`
+
+e.g:
+
+ >>> expect(obj).method(1, 2, 3)
+ >>> expect(obj).method(1, 2, 3, foo='bar').once()
+ >>> expect(obj).method()
+
+Argument Constraints
+^^^^^^^^^^^^^^^^^^^^
+
+You don't have to pass in the exact arguments. You can use matchers, or even your own function:
+
+ >>> expect(obj).method(any_string)
+ >>> expect(obj).method(not_(any_int), **kwargs_containing(x=1))
+ >>> expect(obj).method.where(lambda *a, **kw: len(a) + len(kw) == 3)
+
+.. comment to fix vim highlights**
+
+If you're going to use a checking function more than once, you should make a matcher.
+You can either subclass :class:`~mocktest.matchers.base.Matcher`, or use
+the utility :func:`~mocktest.matchers.base.matcher` function.
+
+Post-checking
+^^^^^^^^^^^^^
+Specifying your expectations before anything happens is sometimes not the best
+(or easiest) thing to do.
+
+It's possible to just inspect the state of a stub or mock to see what's happened to it
+so far. :data:`received_calls` provides access to the calls received so far. It is a
+list of :class:`~mocktest.callrecord.Call` objects:
+
+For a mock:
+
+ >>> mock.foo.bar()
+ >>> mock.foo.bar(1, 2, x=3)
+ >>> mock.foo.bar.received_calls
+ [<#Call: ((), {})>, <#Call: ((1, 2), {'x': 3})>]
+
+And for a stubbed method:
+
+ >>> expect(foo).bar
+ >>> foo.bar(1, 2, x=3)
+ >>> foo.bar.received_calls
+ [<#Call: ((1, 2), {'x': 3})>]
+
+
+Testing the mocktest library
+----------------------------
+I use `nosetests <http://code.google.com/p/python-nose/>`_, and just run it from
+the root directory. You probably should too!
+
+Thanks
+------
+`Michael Foord <http://www.voidspace.org.uk/python/mock.html>`_
+
View
7 doc/mocktest.rst
@@ -0,0 +1,7 @@
+Mocktest module
+**************************
+**Note**: (all of these classes and methods are available from the toplevel `mocktest` module, despite the use of fully-qualified names here)
+
+.. automodule:: mocktest
+ :members:
+
View
17 mocktest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" ?>
+<?xml-stylesheet type='text/xsl' href='interface.xsl'?>
+<interface uri="http://gfxmonk.net/dist/0install/mocktest.xml" xmlns="http://zero-install.sourceforge.net/2004/injector/interface">
+ <name>mocktest</name>
+ <summary>mocking library for python, inspired by rspec</summary>
+ <description>
+ </description>
+ <homepage>http://gfxmonk.net/dist/doc/mocktest/doc/</homepage>
+
+ <group main="mocktest">
+ <implementation id="sha1new=4b4c1f56d53bb8f16ef6b182e7f6c90e62b3aae8" released="2010-12-28" version="0.5">
+
+ <manifest-digest sha256="97baab0aaa7aff4758c2f7a45ffbe6c89d492b4dc2635feb7b33de9bb4f660c3"/>
+ <archive href="http://gfxmonk.net/dist/0install/mocktest/mocktest-0.5.tgz" size="43051"/>
+ </implementation>
+ </group>
+</interface>
View
104 mocktest/__init__.py
@@ -1,7 +1,107 @@
+"""
+.. automodule:: mocktest.mocktest
+ :members:
+
+.. automodule:: mocktest.mocking
+ :members:
+
+
+.. _setting-expectations:
+
+Setting expectations
+--------------------
+
+The return type from :func:`when` and :func:`expect` is of type MockAct.
+
+.. class:: mocktest.mocking.MockAct
+
+ .. method:: __call__(*a, **kw)
+
+ Set the conditions under which this act will apply. Arguments can
+ be normal objects (checked for equality), or :class:`Matcher` instances.
+
+ .. method:: where(condition_func)
+
+ Match only when `condition_func` returns true, when called with the same
+ arguments as this method.
+
+ .. method:: exactly(number)
+
+ Expect this act to be triggered exactly `number` times.
+ Usually followed by `times()` for readability, as in:
+ >>> expect(obj).meth.exactly(3).times()
+
+ .. method:: at_least(number)
+
+ Expect this act to match at least `number` times.
+
+ .. method:: at_most(number)
+
+ Expect this act to match at most `number` times.
+
+ .. method:: between(lower, upper)
+
+ Expect this act to match between `lower` and `upper` times.
+
+ .. method:: never()
+
+ Alias for exactly(0).times()
+
+ .. method:: once()
+
+ Alias for exactly(1).time()
+
+ .. method:: twice()
+
+ Alias for exactly(2).times()
+
+ .. method:: thrice()
+
+ Alias for exactly(3).times()
+
+ .. method:: and_return(result)
+
+ When this act matches, return the given `result` to the caller.
+
+ .. method:: and_raise(exc)
+
+ When this act matches, raise the given exception instance.
+
+ .. method:: and_call(func)
+
+ When this act matches, call the given `func` and return its value.
+
+ **Note:** `and_raise`, `and_return` and `and_call` each have a `then_*` alias
+ for better readability when using `when`. e.g:
+
+ >>> expect(obj).foo().and_return(True)
+
+ Is readable, however when using `when`, the following is more readable:
+
+ >>> when(obj).foo().then_return(True)
+
+ Both `and_` and `then_` versions have the same effect however.
+
+
+
+
+.. class mocktest.mocking.StubbedMethod
+This is the type that mocktest uses as a stand-in for a replaced (stubbed) method. For example:
+ >>> when(obj).method().then_return(True)
+ :members:
+
+.. automodule:: mocktest.transaction
+ :members:
+
+.. automodule:: mocktest.matchers
+ :members:
+
+.. automodule:: mocktest.callrecord
+ :members:
+
+"""
from core import *
from mocktest import *
from mocking import *
from matchers import *
from callrecord import *
-
-__version__ = '0.3.0'
View
29 mocktest/callrecord.py
@@ -1,8 +1,28 @@
+"""
+Call Records
+------------
+"""
import sys, traceback, os
__all__ = ['Call']
class Call(object):
+ """
+ An encapsulation of call arguments.
+ Can compare for equality with a tuple of
+ (args, kwargs), e.g:
+
+ >>> Call.like(1,2,3, x=4) == ((1,2,3), {'x':4})
+ True
+
+ :param args: non-keyword arguments
+ :type args: tuple
+ :param kwargs: keyword arguments
+ :type kwargs: dict
+ :param stack: If True, a stack trace is captured for reporting \
+ where a given call was made.
+ :members: args, kwargs
+ """
def __init__(self, args, kwargs, stack = False):
self.tuple = (args, kwargs)
self.args = args
@@ -13,6 +33,7 @@ def __init__(self, args, kwargs, stack = False):
@classmethod
def like(cls, *a, **kw):
+ """capture a call with the given arguments"""
return cls(a, kw)
def _concise_stack(self):
@@ -25,6 +46,9 @@ def _concise_stack_line(self, line):
file_ = os.path.basename(file_)
return "%s:%-3s :: %s" % (file_, line, code)
+ def __hash__(self):
+ return hash(self.tuple)
+
def __eq__(self, other):
other_tuple = None
if isinstance(other, type(self)):
@@ -41,7 +65,7 @@ def __ne__(self, other):
return not self.__eq__(other)
def play(self, function):
- print repr((self.args, self.kwargs))
+ """apply this call's arguments to the given callable"""
return function(*self.args, **self.kwargs)
def desc(self, include_stack=False):
@@ -61,3 +85,6 @@ def desc(self, include_stack=False):
def __str__(self):
return self.desc(include_stack=True)
+ def __repr__(self):
+ return "<#Call: %r>" % (self.tuple,)
+
View
15 mocktest/matchers/__init__.py
@@ -1,3 +1,18 @@
+"""
+Argument Matchers
+-----------------
+.. automodule:: mocktest.matchers.base
+ :members:
+
+.. automodule:: mocktest.matchers.type_matcher
+ :members:
+
+.. automodule:: mocktest.matchers.string
+ :members:
+
+.. automodule:: mocktest.matchers.collections
+ :members:
+"""
from base import *
from type_matcher import *
from string import *
View
66 mocktest/matchers/base.py
@@ -1,9 +1,39 @@
-__all__ = ['Matcher', 'Any', 'Not', 'SplatMatcher']
+"""
+Basic Matchers
+--------------
+
+.. data:: Any
+A matcher instance that matches any object.
+If called with a type, e.g.:
+ >>> Any(int)
+
+It returns a matcher for any instance of that type.
+
+.. data:: _any
+Alias for :data:`Any`
+
+
+.. function:: Not(matcher)
+Call with a matcher to return a matcher inverting the logic of tha passed-in matcher.
+e.g.:
+ >>> Not(Any(str))
+
+Would match anything that isn't a string instance.
+
+.. data:: _not
+Alias for :data:`Not`
+"""
+
+__all__ = ['Matcher', 'Any', 'any_', 'Not', 'not_', 'SplatMatcher', 'KwargsMatcher', 'matcher']
class Matcher(object):
+ """
+ Base matcher class
+ """
_desc = 'unnamed matcher'
def desc(self):
+ """return a description of this matcher"""
return self._desc
def __str__(self):
@@ -19,8 +49,9 @@ def __iter__(self):
def __getitem__(self, name):
return None
- def items(self):
- return ('x',1)
+ def matches(self, obj):
+ """return True if this matcher is satisfied by the given object, else False"""
+ raise AssertionError("matcher has not overidden `matches`!")
class NegatedMatcher(Matcher):
def __init__(self, orig):
@@ -35,18 +66,15 @@ def desc(self):
return 'not %s' % (self.orig.desc(),)
def matcher(matches, desc = 'anonymous matcher'):
- return type('Matcher', (Matcher,), {'matches':matches, 'desc': lambda self: desc})()
-
-
-class AnyInstanceOf(Matcher):
- def __init__(self, cls):
- self._cls = cls
+ """
+ Create a matcher
- def desc(self):
- return "any instance of %r" % (self._cls,)
+ :param matches: given one argument (the subject), this function should return either True or False
+ :type matches: callable
+ :param desc: Human readable desription of this matcher's logic, e.g "a positive number"
+ """
+ return type('Matcher', (Matcher,), {'matches':matches, 'desc': lambda self: desc})()
- def matches(self, other):
- return isinstance(other, self._cls)
class SplatMatcherMaker(Matcher):
def __init__(self, matcher):
@@ -77,11 +105,17 @@ def desc(self):
return "each argument is [%r]" % (self._matcher.desc(),)
class AnyObject(Matcher, dict):
+ """Matches any object.
+ If called and given a type, returns a :py:class:mocktest.matchers.type_matcher.TypeMatcher: instance for that type
+ """
def __init__(self):
dict.__init__(self, __kwargs=self)
- def __call__(self, cls):
- return AnyInstanceOf(cls)
+ def __call__(self, cls=None):
+ if cls is None:
+ return self
+ from type_matcher import TypeMatcher
+ return TypeMatcher(cls)
def matches(self, other):
return True
@@ -102,5 +136,7 @@ def matches(self, *a):
args_with = SplatMatcherMaker
Not = NegatedMatcher
+not_ = NegatedMatcher
Any = AnyObject()
+any_ = Any
View
23 mocktest/matchers/collections.py
@@ -1,3 +1,26 @@
+"""
+Collection Matchers
+-------------------
+
+.. function:: any_of(*elements)
+Matches an argument if it is any of the listed elements
+
+.. function:: object_containing(*elements)
+Matches an object that includes all given elements.
+
+.. function:: args_containing(*args)
+Just like :func:`object_containing`, but used for matching args. E.g:
+ >>> expect(obj).meth(*args_containing(2, 3))
+
+.. function:: dict_containing(**kwargs)
+Matches a mapping with at least the given keys and values.
+
+.. function:: kwargs_containing(**kwargs)
+Just like :func:`dict_containing`, but used for matching kwargs. E.g:
+ >>> expect(obj).meth(1, 2, 3, **kwargs_containing(do_frob=True))
+
+
+"""
from base import Matcher, KwargsMatcher, SplatMatcherMaker
__all__ = [
'object_containing',
View
8 mocktest/matchers/string.py
@@ -1,3 +1,11 @@
+"""
+String Matchers
+---------------
+
+.. function:: string_matching(pattern)
+Takes either a string or a compiled regex pattern.
+"""
+
__all__ = [
'string_matching'
]
View
15 mocktest/matchers/type_matcher.py
@@ -1,10 +1,22 @@
+"""
+Type matchers
+-------------
+.. data:: any_string
+.. data:: any_int
+.. data:: any_float
+.. data:: any_dict
+.. data:: any_list
+
+.. function:: object_with(attribute_name)
+Match an object that has an attribute named `attribute_name`.
+
+"""
__all__ = [
'any_string',
'any_int',
'any_float',
'any_dict',
'any_list',
- 'any_',
'object_with'
]
@@ -30,7 +42,6 @@ def matches(self, other):
def desc(self):
return "any object with attribute \"%s\"" % (self.attr_name,)
-any_ = TypeMatcher
any_string = TypeMatcher(str)
any_int = TypeMatcher(int)
any_float = TypeMatcher(float)
View
43 mocktest/mocking.py
@@ -1,3 +1,7 @@
+"""
+Mock Objects
+------------
+"""
from matchers import Matcher, SplatMatcher
from mockerror import MockError
from callrecord import Call
@@ -14,22 +18,42 @@
]
def when(obj):
+ """
+ Replace a method on an object. Just like `expect`, except
+ that no verification against the number of calls received
+ will be performed.
+ """
return GetWrapper(lambda name: mock_when(obj, name))
def expect(obj):
+ """
+ Add an expectation to a method of `obj`.
+ By default, the method is expected at least once.
+ E.g:
+ >>> expect(some_object).method
+ """
return GetWrapper(lambda name: mock_expect(obj, name))
def mock(name='unnamed mock', create_children=True):
"""
+ Make a mock object.
+
:param name: the name of this mock object
:param create_children: when attributes are accessed on this
- Make a mock object.
- mock, they will be created by default. Set this to False to
- raise an AttributeError instead
+ mock, they will be created by default. Set this to False to
+ raise an AttributeError instead
"""
return RecursiveStub(name, create_children)
def modify(obj):
+ """
+ Replace children of an existing object for the duration of
+ this test. E.g:
+ >>> modify(obj).child = replacement_child
+ >>> modify(obj).grand.child = replacement_grand_child
+
+ All replaced attributes will be reverted when the test completes.
+ """
replacements = []
def replace_(name, val):
replace_attr(obj, name, val, generate_reset=len(replacements)==0)
@@ -296,16 +320,6 @@ def thrice(self):
"""alias for exactly(3).times"""
return self.exactly(3)
- # overloading
- def __eq__(self, other):
- """
- overloaded operator for comparing to True or False
- """
- return self._matches() == other
-
- def __nonzero__(self):
- return self._matches()
-
def __assert_not_set(self, var, msg="this value"):
if var is not None:
raise MockError("%s has already been set" % (msg,))
@@ -405,16 +419,15 @@ def and_return(self, val):
self._action = lambda *a, **k: val
return self
then_return = and_return
- returning = and_return
def and_call(self, func):
self._action = func
return self
then_call = and_call
- calling = and_call
def and_raise(self, exc):
def _do_raise(*a, **kw):
raise exc
self._action = _do_raise
return self
+ then_raise = and_raise
View
36 mocktest/mocktest.py
@@ -1,15 +1,7 @@
"""
-mocktest includes:
-
- - TestCase: a subclass of unittest.TestCase with the following additions:
- - core._setup and _teardown are automatically called between tests,
- ensuring that expectations on mock objects are always checked
- - enhanced versions of assertTrue / False, assertRaises
-
- - pending annotation: the test case is run but is allowed to fail
- - ignore annotation: the test case is not run
+Test Infrastructure
+-------------------
"""
-
__all__ = (
'TestCase',
'pending',
@@ -59,6 +51,10 @@ def copy_attr(name):
return cls
def Skeleton(cls):
+ """
+ Generate a subclass inheriting only private methods and setUp/tearDown, for the purpose
+ of inheriting test setup but not any actual test implementations
+ """
import mocktest
return subclass_only(cls, ('setUp', 'tearDown'), safe_superclasses=(unittest.TestCase, object, mocktest.TestCase))
@@ -92,6 +88,11 @@ def decorator(callable_):
@ParamDecorator
def pending(function, reason = None):
+ """
+ A decorator for pending tests.
+ Note that pending tests are always run, and will cause a failure if they succeed (a pending test is assumed to be in a failing state.
+ To not run a test at all. use :py:func:mocktest.ignore:
+ """
reason_str = "" if reason is None else " (%s)" % reason
@wraps(function)
def actually_call_it(*args, **kwargs):
@@ -111,6 +112,7 @@ def actually_call_it(*args, **kwargs):
@ParamDecorator
def ignore(function, reason = None):
+ """a decorator for tests that should not be run"""
reason_str = "" if reason is None else " (%s)" % reason
@wraps(function)
def actually_call_it(*args, **kwargs):
@@ -119,6 +121,14 @@ def actually_call_it(*args, **kwargs):
return actually_call_it
class TestCase(unittest.TestCase):
+ """
+ A subclass of unittest.TestCase with the following additions:
+
+ - Automatically calls MockTransaction.__enter__ and __exit__ in \
+ order to reset mock state and verify expectations upon test \
+ completion.
+ - enhanced versions of assertTrue / False, assertRaises
+ """
pending = globals()['pending']
ignore = globals()['ignore']
def __init__(self, methodName = 'runTest'):
@@ -155,10 +165,6 @@ def assertFalse(self, expr, desc = None):
super(TestCase, self).assertFalse(expr, desc)
def assertEqual(self, a, b, desc = None):
- """
- Enhanced assertEquals, prints out more information when
- comparing two dicts or two arrays
- """
if a == b:
return
if desc is not None:
@@ -265,7 +271,7 @@ def assertRaises(self, exception, func, message = None, args = None, kwargs = No
def run(self, result=None):
"""
- this is (mostly) the default implementation of unittest.run
+ This is (mostly) the default implementation of unittest.run
the only modification is that a `self.FailureException` raised
in the teardown method counts for a failure
"""
View
12 mocktest/transaction.py
@@ -1,8 +1,18 @@
+"""
+Mock Transaction
+----------------
+"""
from mockerror import MockError
__unittest = True
class MockTransaction(object):
+ """
+ A context manager to encapsulate a single mocktest transaction.
+
+ **Note**: this is a global context manager - you
+ cannot have more than one.
+ """
teardown_actions = None
started = False
@classmethod
@@ -11,12 +21,14 @@ def add_teardown(cls, func):
@classmethod
def __enter__(cls):
+ """begin a new transaction"""
if cls.started: raise MockError("MockTransaction started while already in progress!")
cls.teardown_actions = []
cls.started = True
@classmethod
def __exit__(cls, *optional_err_info):
+ """end the current transaction, resetting all mocks and verifying all expectations"""
if not cls.started: raise MockError("MockTransaction is not in progress!")
errors = []
for action in reversed(cls.teardown_actions):
View
31 readme.rst
@@ -0,0 +1,31 @@
+mocktest
+********
+... is a powerful and easy-to-use mocking library, inspired by rspec and
+similar in some ways to Michael Foord's popular Mock module.
+
+The main features are:
+
+ - expressive DSL for specifying expectations, stubs and replacement objects
+ - powerful expectation matching behaviour for mock objects
+ - automatic verification of expectations (before each test's tearDown)
+ - automatic rollback of inserted mock objects after each test
+ - descriptive error messages when something fails
+
+
+It's released under the GPLv3 licence (see the LICENCE file).
+
+| Source / Readme / Issues:
+| http://github.com/gfxmonk/mocktest/tree/master
+
+| Zero install feed:
+| http://gfxmonk.net/dist/0install/mocktest.xml
+| (this is the preferred distribution method)
+
+| Cheese shop entry:
+| http://pypi.python.org/pypi/mocktest
+
+
+Documentation
+-------------
+Please see the full documentation online at:
+http://gfxmonk.net/dist/doc/mocktest/doc/
View
7 setup.py
@@ -4,12 +4,11 @@
setup(
name='mocktest',
- version='0.3.1',
+ version='0.5',
description='a mocking and expectation test library for python, inspired by rspec',
author='Tim Cuthbertson',
author_email='tim3d.junk+mocktest@gmail.com',
- url='http://github.com/gfxmonk/mocktest/tree',
- packages=find_packages(exclude=["test"]),
+ url='http://gfxmonk.net/dist/0install/mocktest.xml',
long_description="""\
mocktest is a mocking and expectation test library for python.
@@ -26,7 +25,7 @@
"Topic :: Software Development :: Testing",
],
keywords='test mock expect expectation stub rspec unittest',
- license='BSD',
+ license='GPLv3',
install_requires=[
'setuptools',
],
View
2  test/mocking_test.py
@@ -299,7 +299,7 @@ def __call__(self):
raise RuntimeError("shouldn't actually be called!")
base = Base()
- obj = mock('foo', create_unknown_children=False)
+ obj = mock('foo', False)
modify(obj).copying(base).children(x=1)
assert obj.three_args(1,2,3) == None
assert obj._private() == None
View
14 test/mocktest_test.py
@@ -4,7 +4,6 @@
import os
import mocktest
-import unittest
from mocktest import core, modify, when, MockTransaction, mock, expect, ignore, pending
def assert_desc(expr):
@@ -303,19 +302,6 @@ def test_raise_mismatch_regex(s):
s.assertRaises(RuntimeError, self.make_error, matches='^a')
result = self.run_method(test_raise_mismatch_regex)
self.assertFalse(result.wasSuccessful())
-
- def test_expectation_formatting(self):
- m = mock()
- expectation = expect(m).__call__('foo', bar=1).twice()
- m('foo', bar=1)
- m('foo', bar=1)
-
- self.assertEqual(
- repr(expectation).strip(),
- '\n'.join([
- 'Mock "__call__" has not yet checked expectations:',
- ' expected exactly 2 calls with arguments equal to: (\'foo\', bar=1)'])
- )
def test_reality_formatting(self):
core._teardown()
Please sign in to comment.
Something went wrong with that request. Please try again.