Commits

Nat Williams committed a32a8a3

more docs for without_args and arg_not methods

Comments (0)

Files changed (3)

docs/using-fudge.rst

     ...     )
     ...     api.UpdateStatus(msg)
     ...     print "Sent: %s" % msg
-    >>> 
+    >>>
 
 Since the `oauthtwitter`_ module is maintained independently, your code should work as long as it calls the right methods.
-    
+
 A Simple Test Case
 ==================
 
 .. _oauthtwitter: http://code.google.com/p/oauth-python-twitter/
 
 .. doctest::
-    
+
     >>> import fudge
     >>> @fudge.patch('oauthtwitter.OAuthApi')
     ... def test(FakeOAuthApi):
     ...                             '<oauth_token>', '<oauth_token_secret>')
     ...                  .returns_fake()
     ...                  .expects('UpdateStatus').with_arg_count(1))
-    ... 
+    ...
     ...     post_msg_to_twitter("hey there fellow testing freaks!")
-    >>> 
+    >>>
 
 Let's break this down:
 
 1. The :func:`patch <fudge.patch>` decorator will temporarily patch in a fake
    object for the duration of the test and expose it as an argument to
    the test.  This allows you to add expectations or stubs.
-2. The :class:`fake <fudge.Fake>` object you see here expects a call (class 
-   instantiation) with four arguments having specific string values.  The 
-   returned value is an object instance (a new fake) that expects you to call 
+2. The :class:`fake <fudge.Fake>` object you see here expects a call (class
+   instantiation) with four arguments having specific string values.  The
+   returned value is an object instance (a new fake) that expects you to call
    ``fake_oauth.UpdateStatus()`` with one argument.
 3. Finally, ``post_msg_to_twitter()`` is called.
 
 Let's run the test!
 
 .. doctest::
-  
+
   >>> test()
   Sent: hey there fellow testing freaks!
 
 Fake objects without patches (dependency injection)
 ===================================================
 
-If you don't need to patch anything, you can use the :func:`fudge.test` 
-decorator instead.  This will ensure an exception is raised in case any 
+If you don't need to patch anything, you can use the :func:`fudge.test`
+decorator instead.  This will ensure an exception is raised in case any
 expectations aren't met.  Here's an example:
 
 .. doctest::
-    
+
     >>> def send_msg(api):
     ...     if False: # a mistake
     ...         api.UpdateStatus('hello')
-    ... 
+    ...
     >>> @fudge.test
     ... def test_msg():
     ...     FakeOAuthApi = (fudge.Fake('OAuthApi')
     ...                          .expects('UpdateStatus'))
     ...     api = FakeOAuthApi()
     ...     send_msg(api)
-    ... 
+    ...
     >>> test_msg()
     Traceback (most recent call last):
     ...
 
 .. doctest::
     :hide:
-    
+
     >>> fudge.clear_expectations()
 
 Stubs Without Expectations
 ==========================
 
-If you want a fake object where the methods can be called but are not 
-expected to be called, the code is just the same but instead of 
-:meth:`Fake.expects() <fudge.Fake.expects>` you use :meth:`Fake.provides() <fudge.Fake.provides>`.  Here is an example of always returning True 
+If you want a fake object where the methods can be called but are not
+expected to be called, the code is just the same but instead of
+:meth:`Fake.expects() <fudge.Fake.expects>` you use :meth:`Fake.provides() <fudge.Fake.provides>`.  Here is an example of always returning True
 for the method is_logged_in():
 
 .. doctest::
     :hide:
-    
+
     >>> import fudge
 
 .. doctest::
     ...         print "Bird is the word"
     ...     else:
     ...         print "Access denied"
-    ... 
+    ...
     >>> @fudge.patch('auth.current_user')
     ... def test_secret_word(current_user):
     ...     user = current_user.expects_call().returns_fake()
     ...     user = user.provides('is_logged_in').returns(True)
     ...     show_secret_word()
-    ... 
+    ...
     >>> test_secret_word()
     Bird is the word
 
 Replacing A Method
 ==================
 
-Sometimes returning a static value isn't good enough, you actually need to run some code.  
+Sometimes returning a static value isn't good enough, you actually need to run some code.
 You can do this using :meth:`Fake.calls() <fudge.Fake.calls>` like this:
 
 .. doctest::
-    
+
     >>> auth = fudge.Fake()
     >>> def check_user(username):
     ...     if username=='bert':
     ...         print "Bird is the word"
     ...     else:
     ...         print "Access denied"
-    ... 
+    ...
     >>> auth = auth.provides('show_secret_word_for_user').calls(check_user)
     >>> # Now, the check_user function gets called instead:
     >>> auth.show_secret_word_for_user("bert")
 Some objects you might want to work with will spawn a long chain of objects.  Here is an example of fudging a cascading `SQLAlchemy query <http://www.sqlalchemy.org/docs/05/ormtutorial.html#querying>`_.  Notice that :meth:`Fake.returns_fake() <fudge.Fake.returns_fake>` is used to specify that ``session.query(User)`` should return a new object.  Notice also that because query() should be iterable, it is set to return a list of fake User objects.
 
 .. doctest::
-    
+
     >>> import fudge
     >>> session = fudge.Fake('session')
     >>> query = (session.provides('query')
     >>> from models import User
     >>> for instance in session.query(User).order_by(User.id):
     ...     print instance.name, instance.lastname
-    ... 
+    ...
     Al Capone
 
 Multiple Return Values
 Let's say you want to test code that needs to call a function multiple times and get back multiple values.  Up until now, you've just seen the :meth:`Fake.returns() <fudge.Fake.returns>` method which will return a value infinitely.  To change that, call :meth:`Fake.next_call() <fudge.Fake.next_call>` to advance the call sequence.  Here is an example using a shopping cart scenario:
 
 .. doctest::
-    
+
     >>> cart = (fudge.Fake('cart')
     ...              .provides('add')
     ...              .with_args('book')
 Expecting A Specific Call Order
 ===============================
 
-You may need to test an object that expects its methods to be called in a specific order. 
+You may need to test an object that expects its methods to be called in a specific order.
 Just preface any calls to :func:`fudge.Fake.expects` with :func:`fudge.Fake.remember_order` like this:
 
 .. doctest::
-    
+
     >>> import fudge
     >>> session = (fudge.Fake("session").remember_order()
     ...                                 .expects("get_count").returns(0)
     ...                                 .expects("set_count").with_args(5)
     ...                                 .expects("get_count").returns(5))
-    ... 
+    ...
     >>> session.get_count()
     0
     >>> session.set_count(5)
 
 .. doctest::
     :hide:
-    
+
     >>> fudge.clear_calls()
 
 .. doctest::
 
 .. doctest::
     :hide:
-    
+
     >>> fudge.clear_expectations()
 
 .. _creating-a-stub:
 easier to work complex objects.  Here is an example:
 
 .. doctest::
-  
+
   >>> Server = fudge.Fake('xmlrpclib.Server').is_a_stub()
   >>> pypi = Server('http://pypi.python.org/pypi')
   >>> pypi.list_packages()
   fake:base.one.two.three().four
 
 .. note::
-  
+
   When using :func:`fudge.Fake.is_a_stub` you can't lazily access any
   attributes or methods if they have the same name as a Fake method,
   like ``returns()`` or ``with_args()``.  You would need to declare
 Working with Arguments
 ======================
 
-The :func:`fudge.Fake.with_args` method optionally allows you to declare expectations of 
-how arguments should be sent to your object.  It's usually sufficient to expect an exact 
+The :func:`fudge.Fake.with_args` method optionally allows you to declare expectations of
+how arguments should be sent to your object.  It's usually sufficient to expect an exact
 argument value but sometimes you need to use :mod:`fudge.inspector` functions for dynamic values.
 
 Here is a short example:
 
 .. doctest::
-    
+
     >>> import fudge
     >>> from fudge.inspector import arg
     >>> image = (fudge.Fake("image")
 
 .. doctest::
     :hide:
-    
+
     >>> fudge.verify()
     >>> fudge.clear_expectations()
-    
+
+The :class:`Fake <fudge.Fake>` class also provides a :meth:`without_args <fudge.Fake.without_args>` method, which functions just the opposite.
+With it, you can declare arguments that you expect NOT to be provided.
+
+.. doctest::
+
+    >>> image = (fudge.Fake('image')
+    ...               .expects('save')
+    ...               .without_args('GIF', filename=arg.endswith('.gif')))
+
+This expectation will pass for any call that does not provide the string ``'GIF'``
+as a positional argument and does not provide a ``filename`` keyword argument that
+ends in ``'.gif'``
+
+.. doctest::
+
+    >>> image.save('JPEG', filename="funny_cat6.jpg")
+    >>> image.save('total nonsense', {'fizz': 'buzz'})
+
+There also inverted version of all the :class:`fudge.inspector.arg <fudge.inspector.ValueInspector>` methods, available on the ``fudge.inspector.arg_not`` object.
+The methods all have the same name, but assert the opposite of the ``arg`` versions.
+See the docstrings for the various :meth:`fudge.inspector.arg <fudge.inspector.ValueInspector>` methods for examples of their usage.
+
+
+.. doctest::
+    :hide:
+
+    >>> fudge.verify()
+    >>> fudge.clear_expectations()
+
 .. _Nose: http://somethingaboutorange.com/mrl/projects/nose/
 .. _py.test: http://codespeak.net/py/dist/test.html
 
 
 .. toctree::
     :glob:
-    
-    api/*
+
+    api/*

fudge/__init__.py

             >>> query = fudge.Fake('query').expects_call().without_args(
             ...     'http://example.com', name="Steve"
             ... )
+
             >>> query('http://python.org', name="Joe")
             >>> query('http://example.com')
             Traceback (most recent call last):
             ...
             AssertionError: fake:query() was called unexpectedly with arg http://example.com
+            >>> query("Joe", "Frank", "Bartholomew", "Steve")
+            >>> query(name='Steve')
+            Traceback (most recent call last):
+            ...
+            AssertionError: fake:query() was called unexpectedly with kwarg name=Steve
             >>> query('http://python.org', name='Steve')
             Traceback (most recent call last):
             ...
             AssertionError: fake:query() was called unexpectedly with kwarg name=Steve
+            >>> query(city='Chicago', name='Steve')
+            Traceback (most recent call last):
+            ...
+            AssertionError: fake:query() was called unexpectedly with kwarg name=Steve
+
+            >>> query.expects_call().without_args('http://example2.com')
+            fake:query
+            >>> query('foobar')
+            >>> query('foobar', 'http://example2.com')
+            Traceback (most recent call last):
+            ...
+            AssertionError: fake:query() was called unexpectedly with arg http://example2.com
+
+            >>> query.expects_call().without_args(name="Hieronymus")
+            fake:query
+            >>> query("Gottfried", "Hieronymus")
+            >>> query(name="Wexter", other_name="Hieronymus")
+            >>> query('asdf', name="Hieronymus")
+            Traceback (most recent call last):
+            ...
+            AssertionError: fake:query() was called unexpectedly with kwarg name=Hieronymus
+            >>> query(name="Hieronymus")
+            Traceback (most recent call last):
+            ...
+            AssertionError: fake:query() was called unexpectedly with kwarg name=Hieronymus
+
+            >>> query = fudge.Fake('query').expects_call().without_args(
+            ...     'http://example.com', name="Steve"
+            ... ).with_args('dog')
+            >>> query('dog')
+            >>> query('dog', 'http://example.com')
+            Traceback (most recent call last):
+            ...
+            AssertionError: fake:query('dog') was called unexpectedly with args ('dog', 'http://example.com')
+            >>> query()
+            Traceback (most recent call last):
+            ...
+            AssertionError: fake:query('dog') was called unexpectedly with args ()
 
         """
         exp = self._get_current_call()

fudge/inspector.py

 
             >>> fudge.clear_expectations()
 
+        The arg_not version will not match anything and is probably not very
+        useful.
+
+        .. doctest::
+
+            >>> import fudge
+            >>> from fudge.inspector import arg_not
+            >>> query = fudge.Fake('query').expects_call().with_args(
+            ...     arg_not.any()
+            ... )
+            >>> query('asdf')
+            Traceback (most recent call last):
+            ...
+            AssertionError: fake:query((NOT) arg.any()) was called unexpectedly with args ('asdf')
+            >>> query()
+            Traceback (most recent call last):
+            ...
+            AssertionError: fake:query((NOT) arg.any()) was called unexpectedly with args ()
+
+        .. doctest::
+            :hide:
+
+            >>> fudge.clear_expectations()
+
         """
         return self._make_value_test(AnyValue)
 
 
             >>> fudge.clear_expectations()
 
+        arg_not.contains matches an argument not containing some element.
+
+        .. doctest::
+
+            >>> from fudge.inspector import arg_not
+            >>> colorpicker = colorpicker.expects('select').with_args(arg_not.contains('blue'))
+            >>> colorpicker.select('reddish')
+            >>> colorpicker.select(['red', 'green'])
+            >>> fudge.verify()
+
+            >>> colorpicker.select('blue-green')
+            Traceback (most recent call last):
+            ...
+            AssertionError: fake:colorpicker.select(arg.contains('red'))[0] was called unexpectedly with args ('blue-green')
+            >>> colorpicker.select(['red', 'blue', 'green'])
+            Traceback (most recent call last):
+            ...
+            AssertionError: fake:colorpicker.select((NOT) arg.contains('blue'))[1] was called unexpectedly with args (['red', 'blue', 'green'])
+
+        .. doctest::
+            :hide:
+
+            >>> fudge.clear_expectations()
+
         """
         return self._make_value_test(Contains, part)
 
 
             >>> fudge.clear_expectations()
 
+        The arg_not version works as expected, matching arguments that do not
+        end with the given element.
+
+        .. doctest::
+
+            >>> from fudge.inspector import arg_not
+            >>> query = fudge.Fake('query').expects_call().with_args(arg_not.endswith('Ringo'))
+            >>> query('John, Paul, George and Steve')
+            >>> fudge.verify()
+
+        .. doctest::
+            :hide:
+
+            >>> fudge.clear_expectations()
+
         """
         return self._make_value_test(Endswith, part)
 
             ...
             AssertionError: fake:db.update(arg.has_attr(first_name='Bob', last_name='James')) was called unexpectedly with args ({'first_name': 'Bob'})
 
+        When called as a method on arg_not, has_attr does the opposite, and
+        ensures that the argument does not have the specified attributes.
+
+        .. doctest::
+
+            >>> from fudge.inspector import arg_not
+            >>> class User:
+            ...     first_name = 'Bob'
+            ...     last_name = 'Dobbs'
+            >>> query = fudge.Fake('query').expects_call().with_args(
+            ...     arg_not.has_attr(first_name='James')
+            ... )
+            >>> query(User())
+            >>> fudge.verify()
 
         .. doctest::
             :hide:
 
             >>> fudge.clear_expectations()
 
+        Using the inverted version, arg_not.passes_test, asserts that the
+        argument does not pass the provided test.
+
+        .. doctest::
+
+            >>> from fudge.inspector import arg_not
+            >>> query = fudge.Fake('query').expects_call().with_args(
+            ...     arg_not.passes_test(lambda x: x > 10)
+            ... )
+            >>> query(5)
+            >>> fudge.verify()
+
+        .. doctest::
+            :hide:
+
+            >>> fudge.clear_expectations()
+
         """
         return self._make_value_test(PassesTest, test)
 
 
             >>> fudge.clear_expectations()
 
+        Using arg_not.startswith instead ensures that arguments do not start
+        with that part.
+
+        .. doctest::
+
+            >>> from fudge.inspector import arg_not
+            >>> query = fudge.Fake('query').expects_call().with_args(
+            ...     arg_not.startswith('asdf')
+            ... )
+            >>> query('qwerty')
+            >>> fudge.verify()
+
+        .. doctest::
+            :hide:
+
+            >>> fudge.clear_expectations()
+
         """
         return self._make_value_test(Startswith, part)