class ClassDouble(InstanceDouble):
    """
    A pure double representing the target class.

    ::

        User = ClassDouble('myapp.User')

    :param str path: The absolute module path to the class.
    """

    is_class = True

    def __init__(self, path):
        super(ClassDouble, self).__init__(path)
        self._doubles_target = patch_class(self._doubles_target)
        self._target = Target(self._doubles_target)

    def __call__(self, *args, **kwargs):
        """Verify arguments and proxy to _doubles__new__

        :rtype obj:
        :raises VerifyingDoubleArgumentError: If args/kwargs don't match the expected arguments of
            __init__ of the underlying class.
        """
        verify_arguments(self._target, '_doubles__new__', args, kwargs)
        return self._doubles__new__(*args, **kwargs)

    def _doubles__new__(self, *args, **kwargs):
        """Raises an UnallowedMethodCallError

        NOTE: This method is here only to raise if it has not been stubbed
        """
        raise UnallowedMethodCallError('Cannot call __new__ on a ClassDouble without stubbing it') def allow(target):
    """
    Prepares a target object for a method call allowance (stub). The name of the method to allow
    should be called as a method on the return value of this function::

        allow(foo).bar

    Accessing the ``bar`` attribute will return an ``Allowance`` which provides additional methods
    to configure the stub.

    :param object target: The object that will be stubbed.
    :return: An ``AllowanceTarget`` for the target object.
    """

    return AllowanceTarget(target) def allow_constructor(target):
    """
    Set an allowance on a ``ClassDouble`` constructor

    This allows the caller to control what a ClassDouble returns when a new instance is created.

    :param ClassDouble target:  The ClassDouble to set the allowance on.
    :return: an ``Allowance`` for the __new__ method.
    :raise: ``ConstructorDoubleError`` if target is not a ClassDouble.
    """
    if not isinstance(target, ClassDouble):
        raise ConstructorDoubleError(
            'Cannot allow_constructor of {} since it is not a ClassDouble.'.format(target),
        )

    return allow(target)._doubles__new__ def expect_constructor(target):
    """
    Set an expectation on a ``ClassDouble`` constructor

    :param ClassDouble target:  The ClassDouble to set the expectation on.
    :return: an ``Expectation`` for the __new__ method.
    :raise: ``ConstructorDoubleError`` if target is not a ClassDouble.
    """
    if not isinstance(target, ClassDouble):
        raise ConstructorDoubleError(
            'Cannot allow_constructor of {} since it is not a ClassDouble.'.format(target),
        )

    return expect(target)._doubles__new__ def teardown():
    """Tears down the current Doubles environment. Must be called after each test case."""
    if hasattr(_thread_local_data, 'current_space'):
        _thread_local_data.current_space.teardown()
        del _thread_local_data.current_space def verify():
    """Verify a mock

    Verifies any mocks that have been created during the test run. Must be called after each
    test case, but before teardown.
    """

    if hasattr(_thread_local_data, 'current_space'):
        _thread_local_data.current_space.verify() class MockExpectationError(AssertionError):
    """An exception raised when a mock fails verification."""

    pass class UnallowedMethodCallError(AssertionError):
    """An exception raised when an unallowed method call is made on a double."""

    pass class VerifyingDoubleError(AssertionError):
    """
    An exception raised when attempting to double a method that does not exist on the real object.
    """

    def __init__(self, method_name, doubled_obj):
        """
        :param str method_name: The name of the method to double.
        :param object doubled_obj: The real object being doubled.
        """

        self._method_name = method_name
        self._doubled_obj = doubled_obj
        self.args = (method_name, doubled_obj)
        self.message = "Cannot double method '{}' of '{}'."

    def no_matching_method(self):
        self.message = "Cannot double method '{}' because {} does not implement it."

        return self

    def not_callable(self):
        self.message = "Cannot double method '{}' because it is not a callable attribute on {}."

        return self

    def requires_instance(self):
        self.message = "Cannot double method '{}' because it is not callable directly on {}."

        return self

    def __str__(self):
        return self.message.format(self._method_name, self._doubled_obj) class VerifyingDoubleArgumentError(AssertionError):
    """
    An exception raised when attempting to double a method with arguments that do not match the
    signature of the real method.
    """

    pass class ConstructorDoubleError(AssertionError):
    """
    An exception raised when attempting to double the constructor of a non ClassDouble.
    """ class User(OldStyleUser, object):
    pass class OldStyleUser():
    """An importable dummy class used for testing purposes."""

    class_attribute = 'foo'
    callable_class_attribute = classmethod(lambda cls: 'dummy result')
    arbitrary_callable = ArbitraryCallable('ArbitraryCallable Value')

    def __init__(self, name, age):
        self.name = name
        self.age = age
        self.callable_instance_attribute = lambda: 'dummy result'

    @staticmethod
    def static_method(arg):
        return 'static_method return value: {}'.format(arg)

    @classmethod
    def class_method(cls, arg):
        return 'class_method return value: {}'.format(arg)

    def get_name(self):
        return self.name

    def instance_method(self):
        return 'instance_method return value'

    def method_with_varargs(self, *args):
        return 'method_with_varargs return value'

    def method_with_default_args(self, foo, bar='baz'):
        return 'method_with_default_args return value'

    def method_with_varkwargs(self, **kwargs):
        return 'method_with_varkwargs return value'

    def method_with_positional_arguments(self, foo):
        return 'method_with_positional_arguments return value'

    def method_with_doc(self):
        """A basic method of OldStyleUser to illustrate existence of a docstring"""
        return

    @property
    def some_property(self):
        return 'some_property return value'

    def __call__(self, *args):
        return 'user was called'

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        pass class EmptyClass(OldStyleEmptyClass, object):
    pass class OldStyleEmptyClass():
    pass
import re
from pytest import raises, mark
from doubles import allow, allow_constructor, expect_constructor, ClassDouble
from doubles.lifecycle import teardown, verify
from doubles.exceptions import (
    MockExpectationError,
    UnallowedMethodCallError,
    VerifyingDoubleError,
    VerifyingDoubleArgumentError,
    ConstructorDoubleError,
)
from doubles.testing import (
    User,
    OldStyleUser,
    EmptyClass,
    OldStyleEmptyClass,
)
        User = ClassDouble('doubles.testing.User')

        with raises(VerifyingDoubleError) as e:
            allow(User).instance_method

        assert re.search(r"not callable directly on", str(e))


class TestUsingStubbingConstructor(object):
    @mark.parametrize('test_class', TEST_CLASSES)
    class TestUserAndEmptyClass(object):
        """Run test against User and EmptyClass"""

        def test_allowing_with_args(self, test_class):
            TestClass = ClassDouble(test_class)

            allow_constructor(TestClass).with_args(*VALID_ARGS[test_class]).and_return('Bob Barker')

            assert TestClass(*VALID_ARGS[test_class]) == 'Bob Barker'

        def test_with_no_allowances(self, test_class):
            TestClass = ClassDouble(test_class)

            with raises(UnallowedMethodCallError):
                TestClass(*VALID_ARGS[test_class])

        def test_unsatisfied_expectation(self, test_class):
            TestClass = ClassDouble(test_class)

            expect_constructor(TestClass)
            with raises(MockExpectationError):
                verify()
            teardown()

        def test_satisfied_exception(self, test_class):
            TestClass = ClassDouble(test_class)

            expect_constructor(TestClass)

            TestClass(*VALID_ARGS[test_class])

    @mark.parametrize('test_class', TEST_CLASSES[0:2])
    class TestUserOnly(object):
        """Test using the constructor on a class with a custom __init__"""

        def test_called_with_wrong_args(self, test_class):
            TestClass = ClassDouble(test_class)

            allow_constructor(TestClass).with_args(*VALID_ARGS[test_class]).and_return('Bob Barker')

            with raises(UnallowedMethodCallError):
                TestClass('Bob', 101)


class TestStubbingConstructor(object):
    @mark.parametrize('test_class', TEST_CLASSES)
    class TestAllow(object):
        def test_with_no_args(self, test_class):
            TestClass = ClassDouble(test_class)

            allow_constructor(TestClass)
            assert TestClass(*VALID_ARGS[test_class]) is None

        def test_with_invalid_args(self, test_class):
            TestClass = ClassDouble(test_class)

            with raises(VerifyingDoubleArgumentError):
                allow_constructor(TestClass).with_args(10)

        def test_with_valid_args(self, test_class):
            TestClass = ClassDouble(test_class)

            allow_constructor(TestClass).with_args(*VALID_ARGS[test_class]).and_return('Bob')

            assert TestClass(*VALID_ARGS[test_class]) == 'Bob'

    @mark.parametrize('test_class', TEST_CLASSES)
    class TestExpect(object):
        def test_with_no_args(self, test_class):
            TestClass = ClassDouble(test_class)

            expect_constructor(TestClass)
            assert TestClass(*VALID_ARGS[test_class]) is None

        def test_with_invalid_args(self, test_class):
            TestClass = ClassDouble(test_class)

            with raises(VerifyingDoubleArgumentError):
                expect_constructor(TestClass).with_args(10)
            teardown()

        def test_with_valid_args(self, test_class):
            TestClass = ClassDouble(test_class)

            expect_constructor(TestClass).with_args(*VALID_ARGS[test_class])

            assert TestClass(*VALID_ARGS[test_class]) is None


@mark.parametrize('test_class', [User, OldStyleUser, EmptyClass, OldStyleEmptyClass])
class TestingStubbingNonClassDoubleConstructors(object):
    def test_raises_if_you_allow_constructor(self, test_class):
        with raises(ConstructorDoubleError):
            allow_constructor(test_class)

    def test_raises_if_you_expect_constructor(self, test_class):
        with raises(ConstructorDoubleError):
            expect_constructor(test_class)


class TestStubbingConstructorOfBuiltinSubClass(object):
    @mark.parametrize('type_', ['Dict'])
    class TestAcceptsKwargs(object):
        def test_fails_with_positional_args(self, type_):
            double = ClassDouble('doubles.testing.{}SubClass'.format(type_))
            with raises(VerifyingDoubleArgumentError):
                allow_constructor(double).with_args(1, 2)

        def test_fails_with_positional_args_and_kwargs(self, type_):
            double = ClassDouble('doubles.testing.{}SubClass'.format(type_))

            with raises(VerifyingDoubleArgumentError):