Skip to content

Commit

Permalink
Merge b0d107b into 8eaabd9
Browse files Browse the repository at this point in the history
  • Loading branch information
deathowl committed Oct 15, 2020
2 parents 8eaabd9 + b0d107b commit 6b65237
Show file tree
Hide file tree
Showing 6 changed files with 227 additions and 1 deletion.
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ else
UNITTEST_ARGS := --verbose
endif
TESTS_SRCS = tests
SRCS = testslide util
SRCS = testslide util pytest-testslide
ALL_SRCS = $(TESTS_SRCS) $(SRCS)
TERM_BRIGHT := $(shell tput bold)
TERM_NONE := $(shell tput sgr0)
Expand Down Expand Up @@ -68,6 +68,14 @@ docs_clean:
.PHONY: unittest_tests
unittest_tests: $(TESTS_SRCS)/*_unittest.py

.PHONY: pytest_tets
pytest_tests: FORCE coverage_erase
@printf "${TERM_BRIGHT}PYTEST pytest_testslide${TERM_NONE}"
PYTHONPATH=${CURDIR}/pytest-testslide:${CURDIR} \
${Q} coverage run \
-m pytest \
pytest-testslide/tests

%_testslide.py: FORCE coverage_erase
@printf "${TERM_BRIGHT}TESTSLIDE $@\n${TERM_NONE}"
${Q} coverage run \
Expand Down Expand Up @@ -120,6 +128,7 @@ format_black:
tests: \
unittest_tests \
testslide_tests \
pytest_tets \
mypy \
flake8 \
isort \
Expand Down
3 changes: 3 additions & 0 deletions pytest-testslide/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# pytest-testslide

TestSlide fixture for pytest.
70 changes: 70 additions & 0 deletions pytest-testslide/pytest_testslide.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

from types import TracebackType
from typing import Any, Callable, Iterator, List, Optional

import pytest # type: ignore

import testslide as testslide_module


class _TestSlideFixture:
def _register_assertion(self, assertion: Callable) -> None:
self._assertions.append(assertion)

def __enter__(self) -> "_TestSlideFixture":
self._assertions: List[Callable] = []
testslide_module.mock_callable.register_assertion = self._register_assertion
return self

def __exit__(
self,
exc_type: Optional[type],
exc_val: Optional[Exception],
exc_tb: TracebackType,
):
aggregated_exceptions = testslide_module.AggregatedExceptions()
try:
for assertion in self._assertions:
try:
assertion()
except BaseException as be:
aggregated_exceptions.append_exception(be)

finally:
testslide_module.mock_callable.unpatch_all_callable_mocks()
testslide_module.mock_constructor.unpatch_all_constructor_mocks()
testslide_module.patch_attribute.unpatch_all_mocked_attributes()
if aggregated_exceptions.exceptions:
pytest.fail(str(aggregated_exceptions), False)

@staticmethod
def mock_callable(
*args: Any, **kwargs: Any
) -> testslide_module.mock_callable._MockCallableDSL:
return testslide_module.mock_callable.mock_callable(*args, **kwargs)

@staticmethod
def mock_async_callable(
*args: Any, **kwargs: Any
) -> testslide_module.mock_callable._MockAsyncCallableDSL:
return testslide_module.mock_callable.mock_async_callable(*args, **kwargs)

@staticmethod
def mock_constructor(
*args: Any, **kwargs: Any
) -> testslide_module.mock_constructor._MockConstructorDSL:
return testslide_module.mock_constructor.mock_constructor(*args, **kwargs)

@staticmethod
def patch_attribute(*args: Any, **kwargs: Any) -> None:
return testslide_module.patch_attribute.patch_attribute(*args, **kwargs)


@pytest.fixture
def testslide() -> Iterator[_TestSlideFixture]:
with _TestSlideFixture() as testslide_fixture:
yield testslide_fixture
41 changes: 41 additions & 0 deletions pytest-testslide/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

from setuptools import setup # type: ignore

with open("README.md", encoding="utf8") as f:
readme = f.read()

setup(
name="pytest-testslide",
version="1.0.0",
py_modules=["pytest_testslide"],
maintainer="Fabio Pugliese Ornellas",
maintainer_email="fabio.ornellas@gmail.com",
url="https://github.com/facebookincubator/TestSlide",
license="MIT",
description="TestSlide fixture for pytest",
long_description=readme,
long_description_content_type="text/markdown",
setup_requires=["setuptools>=38.6.0"],
install_requires=[
"testslide>=2.2.1",
"pytest>=5.3.0",
],
extras_require={"build": ["black", "flake8", "mypy"]},
classifiers=[
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3",
"Topic :: Software Development :: Testing",
"Topic :: Software Development :: Testing :: Acceptance",
"Topic :: Software Development :: Testing :: BDD",
"Topic :: Software Development :: Testing :: Mocking",
"Topic :: Software Development :: Testing :: Unit ",
],
entry_points={"pytest11": ["pytest-testslide = pytest_testslide"]},
)
6 changes: 6 additions & 0 deletions pytest-testslide/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

pytest_plugins = "pytester"
97 changes: 97 additions & 0 deletions pytest-testslide/tests/test_pytest_testslide.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
import re


def test_pass(testdir):
testdir.makepyfile(
"""
import time
import pytest
from pytest_testslide import testslide
from tests import sample_module
from testslide import StrictMock
def test_has_mock_callable(testslide):
testslide.mock_callable
def test_mock_callable_assertion_works(testslide):
testslide.mock_callable
def test_mock_callable_unpaches(testslide):
testslide.mock_callable
def test_has_mock_async_callable(testslide):
testslide.mock_async_callable
def test_mock_async_callable_assertion_works(testslide):
testslide.mock_async_callable
def test_mock_async_callable_unpaches(testslide):
testslide.mock_async_callable
def test_has_mock_constructor(testslide):
testslide.mock_constructor
def test_mock_constructor_assertion_works(testslide):
testslide.mock_constructor
def test_mock_constructor_unpaches(testslide):
testslide.mock_constructor
def test_has_patch_attribute(testslide):
testslide.patch_attribute
def test_patch_attribute_unpaches(testslide):
testslide.patch_attribute
def test_mock_callable_patching_works(testslide):
testslide.mock_callable("time", "sleep").to_raise(RuntimeError("Mocked!"))
with pytest.raises(RuntimeError):
time.sleep()
def test_mock_callable_unpatching_works(testslide):
# This will fail if unpatching from test_mock_callable_patching_works does
# not happen
time.sleep(0)
def test_mock_callable_assertion_works(testslide):
testslide.mock_callable("time", "sleep").for_call(0).to_call_original().and_assert_called_once()
time.sleep(0)
def test_mock_callable_failed_assertion_works(testslide):
testslide.mock_callable("time", "sleep").for_call(0).to_call_original().and_assert_called_once()
time.sleep(0)
def test_aggregated_exceptions(testslide):
mocked_cls = StrictMock(sample_module.CallOrderTarget)
testslide.mock_callable(mocked_cls, 'f1')\
.for_call("a").to_return_value("mocked")\
.and_assert_called_once()
testslide.mock_callable(mocked_cls, 'f1')\
.for_call("b").to_return_value("mocked2")\
.and_assert_called_once()
sample_module.CallOrderTarget("c").f1("a")
"""
)
result = testdir.runpytest("-v")
assert "passed, 1 error" in result.stdout.str()
expected_failure = re.compile(
""".*_______________ ERROR at teardown of test_aggregated_exceptions ________________
2 failures.
<class \'AssertionError\'>: calls did not match assertion.
<StrictMock 0x[a-fA-F0-9]+ template=tests.sample_module.CallOrderTarget .*/test_pass0/test_pass.py:[0-9]+>, \'f1\':
expected: called exactly 1 time\(s\) with arguments:
\(\'a\',\)
received: 0 call\(s\)
<class \'AssertionError\'>: calls did not match assertion.
<StrictMock 0x[a-fA-F0-9]+ template=tests.sample_module.CallOrderTarget .*/test_pass0/test_pass.py:[0-9]+>, \'f1\':
expected: called exactly 1 time\(s\) with arguments:
\(\'b\',\)
received: 0 call\(s\).*""",
re.MULTILINE | re.DOTALL,
)
assert expected_failure.match(result.stdout.str())
assert result.ret != 0

0 comments on commit 6b65237

Please sign in to comment.