Test doubles framework for Python
Project description
A powerful test doubles framework for Python.
This started as a try to improve and simplify pyDoubles codebase and API
Source repository is: https://bitbucket.org/DavidVilla/python-doublex
Design principles
Doubles have not public api specific methods. It avoid silent misspelling.
non-proxified doubles does not require collaborator instances, they may use classes
hamcrest.assert_that used for all assertions
Mock invocation order is required by default
Compatible with old and new style classes
Doubles
“free” Stub
# given
stub = Stub()
with stub:
stub.foo('hi').returns(10)
stub.hello(ANY_ARG).returns(False)
stub.bye().raises(SomeException)
# when
result = stub.foo()
# then
assert_that(result, 10)
“verified” Stub
class Collaborator:
def hello(self):
return "hello"
with Stub(Collaborator) as stub
stub.hello().raises(SomeException)
stub.foo().returns(True) # raises ApiMismatch exception
stub.hello(1).returns(2) # raises ApiMismatch exception
“free” Spy
# given
with Spy() as sender:
sender.helo().returns("OK")
# when
sender.send_mail('hi')
sender.send_mail('foo@bar.net')
# then
assert_that(sender.helo(), "OK")
assert_that(sender.send_mail, called())
assert_that(sender.send_mail, called().times(2))
assert_that(sender.send_mail, called_with('foo@bar.net'))
“verified” Spy
class Sender:
def say(self):
return "hi"
def send_mail(self, address, force=True):
[some amazing code]
sender = Spy(Sender)
sender.bar() # raises ApiMismatch exception
sender.send_mail() # raises ApiMismatch exception
sender.send_mail(wrong=1) # raises ApiMismatch exception
sender.send_mail('foo', wrong=1) # raises ApiMismatch exception
ProxySpy
sender = Spy(Sender()) # must give an instance
sender.say('boo!') # raises ApiMismatch exception
assert_that(sender.say(), "hi")
assert_that(sender.say, called())
“free” Mock
with Mock() as smtp:
smtp.helo()
smtp.mail(ANY_ARG)
smtp.rcpt("bill@apple.com")
smtp.data(ANY_ARG).returns(True).times(2)
smtp.helo()
smtp.mail("poormen@home.net")
smtp.rcpt("bill@apple.com")
smtp.data("somebody there?")
smtp.data("I am afraid..")
assert_that(smtp, meets_expectations())
“verified” Mock
class SMTP:
def helo(self):
[...]
def mail(self, address):
[...]
def rcpt(self, address):
[...]
with Mock(STMP) as smtp:
smtp.wrong() # raises ApiMismatch exception
smtp.mail() # raises ApiMismatch exception
stub methods
collaborator = Collaborator()
collaborator.foo = method_returning("bye")
assertEquals("bye", self.collaborator.foo())
collaborator.foo = method_raising(SomeException)
collaborator.foo() # raises SomeException
doublex matchers
called
called() matches any invocation to a method:
spy.Spy()
spy.m1()
spy.m2(None)
spy.m3("hi", 3.0)
spy.m4([1, 2])
assert_that(spy.m1, called())
assert_that(spy.m2, called())
assert_that(spy.m3, called())
assert_that(spy.m4, called())
called_with
called_with() matches specific arguments:
spy.Spy()
spy.m1()
spy.m2(None)
spy.m3("hi", 3.0)
spy.m4([1, 2])
assert_that(spy.m1, called_with())
assert_that(spy.m2, called_with(None))
assert_that(spy.m3, called_with("hi", 3.0))
assert_that(spy.m4, called_with([1, 2]))
matchers, matchers, hamcrest matchers…
doublex support all hamcrest matchers, and their amazing combinations.
checking spied calling args
spy = Spy()
spy.foo("abcd")
assert_that(spy.foo, called_with(has_length(4)))
assert_that(spy.foo, called_with(has_length(greater_than(3))))
assert_that(spy.foo, called_with(has_length(less_than(5))))
assert_that(spy.foo, is_not(called_with(has_length(greater_than(5)))))
stubbing
with Spy() as spy:
spy.foo(has_length(less_than(4))).returns('<4')
spy.foo(has_length(4)).returns('four')
spy.foo(has_length(
all_of(greater_than(4),
less_than(8)))).returns('4<x<8')
spy.foo(has_length(greater_than(8))).returns('>8')
assert_that(spy.foo((1, 2)), is_('<4'))
assert_that(spy.foo('abcd'), is_('four'))
assert_that(spy.foo('abcde'), is_('4<x<8'))
assert_that(spy.foo([0] * 9), is_('>8'))
checking invocation ‘times’
spy.foo() spy.foo(1) spy.foo(1) spy.foo(2) assert_that(spy.never, is_not(called())) # = 0 times assert_that(spy.foo, called()) # > 0 assert_that(spy.foo, called().times(greater_than(0))) # > 0 (same) assert_that(spy.foo, called().times(4)) # = 4 assert_that(spy.foo, called().times(greater_than(2))) # > 2 assert_that(spy.foo, called().times(less_than(6))) # < 6 assert_that(spy.foo, is_not(called_with(5))) # = 0 times assert_that(spy.foo, called_with().times(1)) # = 1 assert_that(spy.foo, called_with(anything())) # > 0 assert_that(spy.foo, called_with(anything()).times(4)) # = 4 assert_that(spy.foo, called_with(1).times(2)) # = 2 assert_that(spy.foo, called_with(1).times(greater_than(1))) # > 1 assert_that(spy.foo, called_with(1).times(less_than(5))) # < 5
Stub observers
Stub observers allow you to execute extra code (similar to python-mock “side effects”):
class Observer(object):
def __init__(self):
self.state = None
def update(self, *args, **kargs):
self.state = args[0]
observer = Observer()
stub = Stub()
stub.foo.attach(observer.update)
stub.foo(2)
assert_that(observer.state, is_(2))
Stub delegates
The value returned by the stub may be delegated to function, method or other callable…:
with Stub() as stub:
stub.foo().delegates(lambda: "hello")
assert_that(stub.foo(), is_("hello"))
It may be delegated to iterators or generators too!:
with Stub() as stub:
stub.foo().delegates([1, 2, 3])
assert_that(stub.foo(), is_(1))
assert_that(stub.foo(), is_(2))
assert_that(stub.foo(), is_(3))
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
File details
Details for the file doublex-0.2.tar.gz.
File metadata
- Download URL: doublex-0.2.tar.gz
- Upload date:
- Size: 9.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e8376422235898413b6911426f19f9b08d18137de81f7d23d18cf62fdaefe27f
|
|
| MD5 |
8859ca4de6e3ec4f7429ccbc143813d6
|
|
| BLAKE2b-256 |
c1b342d2f6e0a3db38f142fd7ea17a21a24f05a82eadcacc0e3b71d3351df32c
|