Skip to content

Commit

Permalink
Merge pull request #72 from sigmavirus24/unittest-integration
Browse files Browse the repository at this point in the history
Unittest Integration
  • Loading branch information
sigmavirus24 committed Jul 16, 2015
2 parents acd7181 + 841c286 commit 2505295
Show file tree
Hide file tree
Showing 8 changed files with 300 additions and 48 deletions.
2 changes: 1 addition & 1 deletion betamax/fixtures/pytest.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
def betamax_session(request):
"""Generate a session that has Betamax already installed.
This will create a new :class:`requests.Sesssion` instance that is already
This will create a new :class:`requests.Session` instance that is already
using Betamax with a generated cassette name. The cassette name is
generated by first using the module name from where the test is collected,
then the class name (if it exists), and then the test function name. For
Expand Down
114 changes: 114 additions & 0 deletions betamax/fixtures/unittest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"""Minimal :class:`unittest.TestCase` subclass adding Betamax integration.
.. autoclass:: betamax.fixtures.unittest.BetamaxTestCase
:members:
When using Betamax with unittest, you can use the traditional style of Betamax
covered in the documentation thoroughly, or you can use your fixture methods,
:meth:`unittest.TestCase.setUp` and :meth:`unittest.TestCase.tearDown` to wrap
entire tests in Betamax.
Here's how you might use it:
.. code-block:: python
from betamax.fixtures import unittest
from myapi import SessionManager
class TestMyApi(unittest.BetamaxTestCase):
def setUp(self):
# Call BetamaxTestCase's setUp first to get a session
super(TestMyApi, self).setUp()
self.manager = SessionManager(self.session)
def test_all_users(self):
\"\"\"Retrieve all users from the API.\"\"\"
for user in self.manager:
# Make assertions or something
Alternatively, if you are subclassing a :class:`requests.Session` to provide
extra functionality, you can do something like this:
.. code-block:: python
from betamax.fixtures import unittest
from myapi import Session, SessionManager
class TestMyApi(unittest.BetamaxTestCase):
SESSION_CLASS = Session
# See above ...
"""
# NOTE(sigmavirus24): absolute_import is required to make import unittest work
from __future__ import absolute_import

import unittest

import requests

from .. import recorder


__all__ = ('BetamaxTestCase',)


class BetamaxTestCase(unittest.TestCase):

"""Betamax integration for unittest.
.. versionadded:: 0.5.0
"""

#: Class that is a subclass of :class:`requests.Session`
SESSION_CLASS = requests.Session

def generate_cassette_name(self):
"""Generates a cassette name for the current test.
The default format is "%(classname)s.%(testMethodName)s"
To change the default cassette format, override this method in a
subclass.
:returns: Cassette name for the current test.
:rtype: str
"""
cls = getattr(self, '__class__')
test = self._testMethodName
return '{0}.{1}'.format(cls.__name__, test)

def setUp(self):
"""Betamax-ified setUp fixture.
This will call the superclass' setUp method *first* and then it will
create a new :class:`requests.Session` and wrap that in a Betamax
object to record it. At the end of ``setUp``, it will start recording.
"""
# Bail out early if the SESSION_CLASS isn't a subclass of
# requests.Session
self.assertTrue(issubclass(self.SESSION_CLASS, requests.Session))
# Make sure if the user is multiply inheriting that all setUps are
# called. (If that confuses you, see: https://youtu.be/EiOglTERPEo)
super(BetamaxTestCase, self).setUp()

cassette_name = self.generate_cassette_name()

self.session = self.SESSION_CLASS()
self.recorder = recorder.Betamax(session=self.session)
self.recorder.use_cassette(cassette_name)
self.recorder.start()

def tearDown(self):
"""Betamax-ified tearDown fixture.
This will call the superclass' tearDown method *first* and then it
will stop recording interactions.
"""
super(BetamaxTestCase, self).tearDown()
self.recorder.stop()
2 changes: 2 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ API

.. automodule:: betamax.fixtures.pytest

.. automodule:: betamax.fixtures.unittest

Examples
--------

Expand Down
12 changes: 10 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@

# 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']
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx']

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
Expand Down Expand Up @@ -220,7 +220,7 @@
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'github3.py', u'github3.py Documentation',
('index', 'betamax', u'Betamax Documentation',
[u'Ian Cordasco'], 1)
]

Expand All @@ -239,3 +239,11 @@

# Documents to append as an appendix to all manuals.
texinfo_appendices = []

# Intersphinx configuration
intersphinx_mapping = {
'python': ('https://docs.python.org/3.4',
(None, 'python-inv.txt')),
'requests': ('http://docs.python-requests.org/en/latest',
(None, 'requests-inv.txt')),
}
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Contents of Betamax's Documentation
record_modes
third_party_packages
usage_patterns
integrations

.. toctree::
:caption: API Documentation
Expand Down
117 changes: 117 additions & 0 deletions docs/integrations.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
Integrating Betamax with Test Frameworks
========================================

It's nice to have a way to integrate libraries you use for testing into your
testing frameworks. Having considered this, the authors of and contributors to
Betamax have included integrations in the package. Betamax comes with
integrations for py.test and unittest. (If you need an integration for another
framework, please suggest it and send a patch!)

PyTest Integration
------------------

.. versionadded:: 0.5.0

When you install Betamax, it now installs a `py.test`_ fixture by default. To
use it in your tests you need only follow the `instructions`_ on pytest's
documentation. To use the ``betamax_session`` fixture for an entire class of
tests you would do:

.. code-block:: python
# tests/test_http_integration.py
import pytest
@pytest.mark.usefixtures('betamax_session')
class TestMyHttpClient:
def test_get(self, betamax_session):
betamax_session.get('https://httpbin.org/get')
This will generate a cassette name for you, e.g.,
``tests.test_http_integration.TestMyHttpClient.test_get``. After running this
test you would have a cassette file stored in your cassette library directory
named ``tests.test_http_integration.TestMyHttpClient.test_get.json``. To use
this fixture at the module level, you need only do

.. code-block:: python
# tests/test_http_integration.py
import pytest
pytest.mark.usefixtures('betamax_session')
class TestMyHttpClient:
def test_get(self, betamax_session):
betamax_session.get('https://httpbin.org/get')
class TestMyOtherHttpClient:
def test_post(self, betamax_session):
betamax_session.post('https://httpbin.org/post')
Unittest Integration
--------------------

.. versionadded:: 0.5.0

When writing tests with unittest, a common pattern is to either import
:class:`unittest.TestCase` or subclass that and use that subclass in your
tests. When integrating Betamax with your unittest testsuite, you should do
the following:

.. code-block:: python
from betamax.fixtures import unittest
class IntegrationTestCase(unitest.BetamaxTestCase):
# Add your the rest of the helper methods you want for your
# integration tests
class SpecificTestCase(IntegrationTestCase):
def test_something(self):
# Test something
The unittest integration provides the following attributes on the test case
instance:

- ``session`` the instance of ``BetamaxTestCase.SESSION_CLASS`` created for
that test.

- ``recorder`` the instance of :class:`betamax.Betamax` created.

The integration also generates a cassette name from the test case class name
and test method. So the cassette generated for the above example would be
named ``SpecificTestCase.test_something``. To override that behaviour, you
need to override the
:meth:`~betamax.fixtures.BetamaxTestCase.generate_cassette_name` method in
your subclass.

If you are subclassing :class:`requests.Session` in your application, then it
follows that you will want to use that in your tests. To facilitate this, you
can set the ``SESSION_CLASS`` attribute. To give a fuller example, let's say
you're changing the default cassette name and you're providing your own
session class, your code might look like:

.. code-block:: python
from betamax.fixtures import unittest
from myapi import session
class IntegrationTestCase(unitest.BetamaxTestCase):
# Add your the rest of the helper methods you want for your
# integration tests
SESSION_CLASS = session.MyApiSession
def generate_cassette_name(self):
classname = self.__class__.__name__
method = self._testMethodName
return 'integration_{0}_{1}'.format(classname, method)
.. _py.test: http://pytest.org/latest/
.. _instructions:
http://pytest.org/latest/fixture.html#using-fixtures-from-classes-modules-or-projects
45 changes: 0 additions & 45 deletions docs/usage_patterns.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,51 +53,6 @@ to not try to record new cassettes or interactions. We also, want to make sure
we're authenticated when possible but that we do not leave our placeholder in
the cassettes when they're replayed.

py.test Integration
-------------------

.. versionadded:: 0.5.0

When you install Betamax, it now installs a `py.test`_ fixture by default. To
use it in your tests you need only follow the `instructions`_ on pytest's
documentation. To use the ``betamax_session`` fixture for an entire class of
tests you would do:

.. code-block:: python
# tests/test_http_integration.py
import pytest
@pytest.mark.usefixtures('betamax_session')
class TestMyHttpClient:
def test_get(self, betamax_session):
betamax_session.get('https://httpbin.org/get')
This will generate a cassette name for you, e.g.,
``tests.test_http_integration.TestMyHttpClient.test_get``. After running this
test you would have a cassette file stored in your cassette library directory
named ``tests.test_http_integration.TestMyHttpClient.test_get.json``. To use
this fixture at the module level, you need only do

.. code-block:: python
# tests/test_http_integration.py
import pytest
pytest.mark.usefixtures('betamax_session')
class TestMyHttpClient:
def test_get(self, betamax_session):
betamax_session.get('https://httpbin.org/get')
class TestMyOtherHttpClient:
def test_post(self, betamax_session):
betamax_session.post('https://httpbin.org/post')
.. _TravisCI: https://travis-ci.org/
.. [#] http://pytest.org/latest/plugins.html
.. _py.test: http://pytest.org/latest/
.. _instructions:
http://pytest.org/latest/fixture.html#using-fixtures-from-classes-modules-or-projects

0 comments on commit 2505295

Please sign in to comment.