diff --git a/.github/workflows/tox-unit-tests.yml b/.github/workflows/tox-unit-tests.yml new file mode 100644 index 0000000..0ff9f86 --- /dev/null +++ b/.github/workflows/tox-unit-tests.yml @@ -0,0 +1,34 @@ +name: Run unit tests with tox + +on: + - push + - pull_request + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + # Note that this list of versions should correspond with what's in tox.ini + python-version: + - '2.7' + - '3.5' + - '3.6' + - '3.7' + - '3.8' + - '3.9' + + steps: + - uses: actions/checkout@v1 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install tox==3.25.0 tox-gh-actions==2.9.1 + - name: Run tox (excluding integration tests) + env: + NOSE_ARGS: -A integration!=1 + run: tox diff --git a/requirements-testing.txt b/requirements-testing.txt index 0065651..55bc314 100644 --- a/requirements-testing.txt +++ b/requirements-testing.txt @@ -1,5 +1,5 @@ -r requirements.txt -mock==1.0.1 -nose==1.3.3 +mock==1.0.1 ; python_version < '3.0' +nose==1.3.7 nose-parameterized==0.3.3 diff --git a/setup.py b/setup.py index 5c320ee..473b124 100755 --- a/setup.py +++ b/setup.py @@ -20,12 +20,6 @@ if line: requirements.append(line.strip("\n")) -test_requirements = [ - "mock", - "nose", - "nose-parameterized", -] - setup( name="gapipy", version=gapipy.__version__, @@ -53,6 +47,4 @@ "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", ], - tests_require=test_requirements, - test_suite="nose.collector", ) diff --git a/tests/test_cache.py b/tests/test_cache.py index 44013c7..181f8a0 100644 --- a/tests/test_cache.py +++ b/tests/test_cache.py @@ -1,10 +1,13 @@ import time from unittest import TestCase, skip, skipUnless -import mock - from gapipy import cache +try: + from unittest import mock # Python 3 +except ImportError: + import mock # Python 2 + try: from django.test import override_settings except ImportError: @@ -46,7 +49,7 @@ def test_gapi_cache_settings_required(self): def test_clear(self): """Should delegate 'clear' operation to django cache client.""" self.client.clear() - self.mock_cache.clear.assert_called_once() + self.assertEqual(len(self.mock_cache.clear.mock_calls), 1) def test_delete(self): """Should delegate 'delete' operation to django cache client.""" diff --git a/tests/test_client.py b/tests/test_client.py index d8ae140..bb1b700 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,5 +1,4 @@ import json -from mock import patch import unittest from gapipy.client import Client @@ -7,6 +6,11 @@ from gapipy.resources.base import Resource from gapipy.utils import get_available_resource_classes +try: + from unittest import mock # Python 3 +except ImportError: + import mock # Python 2 + class ClientTestCase(unittest.TestCase): @@ -37,7 +41,7 @@ class MockResource(Resource): self.assertEqual(resource.id, 1) self.assertEqual(resource.foo, 'bar') - @patch('gapipy.request.APIRequestor._request') + @mock.patch('gapipy.request.APIRequestor._request') def test_create_interface(self, mock_request): class MockResource(Resource): _as_is_fields = ['id', 'foo'] @@ -55,7 +59,7 @@ class MockResource(Resource): resource = self.gapi.create('foo', {'id': 1, 'foo': 'bar', 'context': 'abc'}) self.assertEqual(resource.id, 1) - @patch('gapipy.request.APIRequestor._request') + @mock.patch('gapipy.request.APIRequestor._request') def test_create_extra_headers(self, mock_request): """ Test that extra HTTP headers can be passed through the `.create` @@ -81,7 +85,7 @@ class MockResource(Resource): additional_headers=extra_headers, ) - @patch('gapipy.query.Query.get_resource_data') + @mock.patch('gapipy.query.Query.get_resource_data') def test_correct_client_is_associated_with_resources(self, mock_get_data): mock_get_data.return_value = { 'id': 123 diff --git a/tests/test_query.py b/tests/test_query.py index 7d8c905..08ebe60 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -2,7 +2,6 @@ import sys import unittest -from mock import MagicMock, patch from requests import HTTPError, Response from gapipy.client import Client @@ -15,6 +14,11 @@ PPP_TOUR_DATA, TOUR_DOSSIER_LIST_DATA, DUMMY_DEPARTURE, DUMMY_PROMOTION, ) +try: + from unittest import mock # Python 3 +except ImportError: + import mock # Python 2 + class QueryKeyTestCase(unittest.TestCase): @@ -106,13 +110,13 @@ def setUp(self): self.cache = self.client._cache self.cache.clear() - @patch('gapipy.request.APIRequestor._request', return_value=PPP_TOUR_DATA) + @mock.patch('gapipy.request.APIRequestor._request', return_value=PPP_TOUR_DATA) def test_get_instance_by_id(self, mock_request): query = Query(self.client, Tour) t = query.get(1234) self.assertIsInstance(t, Tour) - @patch('gapipy.request.APIRequestor._request') + @mock.patch('gapipy.request.APIRequestor._request') def test_get_instance_with_forbidden_id(self, mock_request): response = Response() response.status_code = 403 @@ -132,7 +136,7 @@ def test_get_instance_with_forbidden_id(self, mock_request): context.exception.response.status_code, response.status_code) - @patch('gapipy.request.APIRequestor._request') + @mock.patch('gapipy.request.APIRequestor._request') def test_get_instance_with_non_existing_id(self, mock_request): response = Response() response.status_code = 404 @@ -152,7 +156,7 @@ def test_get_instance_with_non_existing_id(self, mock_request): context.exception.response.status_code, response.status_code) - @patch('gapipy.request.APIRequestor._request') + @mock.patch('gapipy.request.APIRequestor._request') def test_get_instance_with_gone_id(self, mock_request): response = Response() response.status_code = 410 @@ -172,7 +176,7 @@ def test_get_instance_with_gone_id(self, mock_request): context.exception.response.status_code, response.status_code) - @patch('gapipy.request.APIRequestor._request') + @mock.patch('gapipy.request.APIRequestor._request') def test_get_instance_by_id_with_non_404_error(self, mock_request): response = Response() response.status_code = 401 @@ -194,7 +198,7 @@ def test_get_instance_by_id_with_non_404_error(self, mock_request): self.assertIsNone( query.get(1234, httperrors_mapped_to_none=[response.status_code])) - @patch('gapipy.request.APIRequestor._request') + @mock.patch('gapipy.request.APIRequestor._request') def test_filtered_query_returns_new_object(self, mock_request): """ Arguments passed to .filter() are stored on new (copied) Query instance @@ -208,7 +212,7 @@ def test_filtered_query_returns_new_object(self, mock_request): self.assertFalse(query is query1) self.assertNotEqual(query._filters, query1._filters) - @patch('gapipy.request.APIRequestor._request') + @mock.patch('gapipy.request.APIRequestor._request') def test_filtered_query(self, mock_request): """ Arguments passed to .filter() are stored on new (copied) Query instance @@ -241,7 +245,7 @@ def test_filtered_query(self, mock_request): query.count() self.assertEqual(len(query._filters), 2) - @patch('gapipy.request.APIRequestor._request') + @mock.patch('gapipy.request.APIRequestor._request') def test_query_persist_filter_on_count(self, mock_request): query = Query(self.client, Tour) my_query = query.filter(tour_dossier_code='PPP') @@ -261,26 +265,26 @@ def test_listing_non_listable_resource_fails(self): with self.assertRaisesRegex(ValueError, message): Query(self.client, Activity).count() - @patch('gapipy.request.APIRequestor._request', return_value=DUMMY_PROMOTION) + @mock.patch('gapipy.request.APIRequestor._request', return_value=DUMMY_PROMOTION) def test_can_retrieve_single_non_listable_resource(self, mock_request): Query(self.client, Activity).get(1234) mock_request.assert_called_once_with( '/activities/1234', 'GET', additional_headers=None) - @patch('gapipy.request.APIRequestor._request', return_value=DUMMY_DEPARTURE) + @mock.patch('gapipy.request.APIRequestor._request', return_value=DUMMY_DEPARTURE) def test_can_retrieve_single_subresource_without_parent(self, mock_request): Query(self.client, Departure).get(1234) mock_request.assert_called_once_with( '/departures/1234', 'GET', additional_headers=None) - @patch('gapipy.request.APIRequestor._request', return_value=TOUR_DOSSIER_LIST_DATA) + @mock.patch('gapipy.request.APIRequestor._request', return_value=TOUR_DOSSIER_LIST_DATA) def test_count(self, mock_request): query = Query(self.client, TourDossier) count = query.count() self.assertIsInstance(count, int) self.assertEqual(count, 3) - @patch('gapipy.request.APIRequestor._request', return_value=TOUR_DOSSIER_LIST_DATA) + @mock.patch('gapipy.request.APIRequestor._request', return_value=TOUR_DOSSIER_LIST_DATA) def test_fetch_all(self, mock_request): query = Query(self.client, TourDossier).all() @@ -296,7 +300,7 @@ def test_fetch_all(self, mock_request): mock_request.assert_called_once_with( '/tour_dossiers', 'GET', params={}) - @patch('gapipy.request.APIRequestor._request', return_value=TOUR_DOSSIER_LIST_DATA) + @mock.patch('gapipy.request.APIRequestor._request', return_value=TOUR_DOSSIER_LIST_DATA) def test_fetch_all_with_limit(self, mock_request): query = Query(self.client, TourDossier).all(limit=2) @@ -341,7 +345,7 @@ def setUp(self): self.cache = self.client._cache self.cache.clear() - @patch('gapipy.request.APIRequestor._request', return_value=PPP_TOUR_DATA) + @mock.patch('gapipy.request.APIRequestor._request', return_value=PPP_TOUR_DATA) def test_resources_are_cached(self, mock_request): query = Query(self.client, Tour) @@ -367,10 +371,10 @@ def test_cached_get_does_not_set(self): query = Query(self.client, Tour) # act like we already have the data in our cache - mock_cache_get = MagicMock(return_value=PPP_TOUR_DATA) + mock_cache_get = mock.MagicMock(return_value=PPP_TOUR_DATA) self.cache.get = mock_cache_get - mock_cache_set = MagicMock() + mock_cache_set = mock.MagicMock() self.cache.set = mock_cache_set query.get(21346) @@ -383,7 +387,7 @@ class MockResource(Resource): _resource_name = 'mocks' -@patch('gapipy.request.APIRequestor._request') +@mock.patch('gapipy.request.APIRequestor._request') class UpdateCreateResourceTestCase(unittest.TestCase): def setUp(self): diff --git a/tests/test_request.py b/tests/test_request.py index bb6467d..397bcf1 100644 --- a/tests/test_request.py +++ b/tests/test_request.py @@ -1,14 +1,17 @@ import sys import unittest -from mock import call, patch - from gapipy.client import Client from gapipy.models.base import _Parent from gapipy.request import APIRequestor from .fixtures import FIRST_PAGE_LIST_DATA, SECOND_PAGE_LIST_DATA +try: + from unittest import mock # Python 3 +except ImportError: + import mock # Python 2 + class Resources(object): def __init__(self, **kwArgs): @@ -21,13 +24,13 @@ def setUp(self): self.client = Client() self.resources = Resources(_resource_name='resources', _uri=None) - @patch('gapipy.request.APIRequestor._request') + @mock.patch('gapipy.request.APIRequestor._request') def test_get_resource_by_id(self, mock_request): requestor = APIRequestor(self.client, self.resources) requestor.get(1234) mock_request.assert_called_once_with('/resources/1234', 'GET', additional_headers=None) - @patch('gapipy.request.APIRequestor._request') + @mock.patch('gapipy.request.APIRequestor._request') def test_get_with_null_resource_id_and_uri_raises_error(self, mock_request): requestor = APIRequestor(self.client, self.resources) error_msg = 'Need to provide at least one of `resource_id` or `uri` as argument' @@ -38,45 +41,45 @@ def test_get_with_null_resource_id_and_uri_raises_error(self, mock_request): with self.assertRaisesRegex(ValueError, error_msg): requestor.get() - @patch('gapipy.request.APIRequestor._request') + @mock.patch('gapipy.request.APIRequestor._request') def test_get_with_falsy_resource_id_does_not_raise_error(self, mock_request): requestor = APIRequestor(self.client, self.resources) requestor.get(0) mock_request.assert_called_once_with('/resources/0', 'GET', additional_headers=None) - @patch('gapipy.request.APIRequestor._request') + @mock.patch('gapipy.request.APIRequestor._request') def test_list_resource(self, mock_request): requestor = APIRequestor(self.client, self.resources) requestor.list_raw() mock_request.assert_called_once_with('/resources', 'GET', params=None) - @patch('gapipy.request.APIRequestor._request') + @mock.patch('gapipy.request.APIRequestor._request') def test_list_raw_uri_requestor_params(self, mock_request): params = {'param': 'value'} requestor = APIRequestor(self.client, self.resources, params=params) requestor.list_raw('/test_uri') mock_request.assert_called_once_with('/test_uri', 'GET', params=params) - @patch('gapipy.request.APIRequestor._request') + @mock.patch('gapipy.request.APIRequestor._request') def test_list_raw_uri_no_requestor_params(self, mock_request): requestor = APIRequestor(self.client, self.resources) requestor.list_raw('/test_uri') mock_request.assert_called_once_with('/test_uri', 'GET', params=None) - @patch('gapipy.request.APIRequestor._request') + @mock.patch('gapipy.request.APIRequestor._request') def test_list_raw_uri_params_requestor_params(self, mock_request): params = {'param': 'value'} requestor = APIRequestor(self.client, self.resources, params=params) requestor.list_raw('/test_uri?') mock_request.assert_called_once_with('/test_uri?', 'GET', params=params) - @patch('gapipy.request.APIRequestor._request') + @mock.patch('gapipy.request.APIRequestor._request') def test_list_raw_uri_params_no_requestor_params(self, mock_request): requestor = APIRequestor(self.client, self.resources) requestor.list_raw('/test_uri?') mock_request.assert_called_once_with('/test_uri?', 'GET', params=None) - @patch('gapipy.request.APIRequestor._request') + @mock.patch('gapipy.request.APIRequestor._request') def test_list_resource_with_parent(self, mock_request): parent = _Parent('parent', '1234', None) requestor = APIRequestor(self.client, self.resources, parent=parent) @@ -84,12 +87,12 @@ def test_list_resource_with_parent(self, mock_request): mock_request.assert_called_once_with( '/parent/1234/resources', 'GET', params=None) - @patch('gapipy.request.APIRequestor.list_raw') + @mock.patch('gapipy.request.APIRequestor.list_raw') def test_list_generator(self, mock_list): mock_list.side_effect = [FIRST_PAGE_LIST_DATA, SECOND_PAGE_LIST_DATA] expected_calls = [ - call(None), - call('http://localhost:5000/resources/?page=2'), + mock.call(None), + mock.call('http://localhost:5000/resources/?page=2'), ] requestor = APIRequestor(self.client, self.resources) @@ -98,7 +101,7 @@ def test_list_generator(self, mock_list): self.assertEqual(mock_list.mock_calls, expected_calls) self.assertEqual(len(resources), 6) - @patch('gapipy.request.APIRequestor._request') + @mock.patch('gapipy.request.APIRequestor._request') def test_uuid_not_set(self, mock_request): self.client.uuid = False requestor = APIRequestor(self.client, self.resources) @@ -106,7 +109,7 @@ def test_uuid_not_set(self, mock_request): requestor.list_raw() mock_request.assert_called_once_with('/resources', 'GET', params={'test': '1234'}) - @patch('gapipy.request.APIRequestor._make_call') + @mock.patch('gapipy.request.APIRequestor._make_call') def test_uuid_set(self, mock_make_call): self.client.uuid = True requestor = APIRequestor(self.client, self.resources) @@ -114,7 +117,7 @@ def test_uuid_set(self, mock_make_call): params_arg = mock_make_call.call_args[0][-1] self.assertTrue('uuid' in params_arg) - @patch('gapipy.request.APIRequestor._make_call') + @mock.patch('gapipy.request.APIRequestor._make_call') def test_uuid_with_other_params(self, mock_make_call): self.client.uuid = True requestor = APIRequestor(self.client, self.resources) diff --git a/tests/test_resources.py b/tests/test_resources.py index 04728d5..cbf75e1 100644 --- a/tests/test_resources.py +++ b/tests/test_resources.py @@ -4,8 +4,6 @@ import datetime from unittest import TestCase -from mock import patch - from gapipy.client import Client from gapipy.constants import DATE_FORMAT from gapipy.models import AccommodationRoom @@ -19,6 +17,10 @@ from .fixtures import DUMMY_DEPARTURE, PPP_TOUR_DATA, PPP_DOSSIER_DATA +try: + from unittest import mock # Python 3 +except ImportError: + import mock # Python 2 class ResourceTestCase(TestCase): @@ -44,7 +46,7 @@ class DatetimeResource(Resource): self.assertEqual(data['date_field'], '2013-02-18') self.assertEqual(data['date_field_utc'], '2013-02-18T18:17:20Z') - @patch('gapipy.request.APIRequestor._request', return_value=PPP_DOSSIER_DATA) + @mock.patch('gapipy.request.APIRequestor._request', return_value=PPP_DOSSIER_DATA) def test_instantiate_from_raw_data(self, _): t = Tour(PPP_TOUR_DATA, client=self.client) self.assertIsInstance(t, Tour) @@ -53,7 +55,7 @@ def test_instantiate_from_raw_data(self, _): self.assertIsInstance(t.tour_dossier, TourDossier) self.assertIsInstance(t.departures, Query) - @patch('gapipy.request.APIRequestor._request') + @mock.patch('gapipy.request.APIRequestor._request') def test_populate_stub(self, mock_fetch): mock_fetch.return_value = { 'id': 1, @@ -81,7 +83,8 @@ def test_populate_stub(self, mock_fetch): # Force a fetch. assert t.departures_start_date - mock_fetch.assert_called_once() + self.assertEqual(len(mock_fetch.mock_calls), 1) + self.assertFalse(t.is_stub) self.assertEqual( t.departures_start_date, @@ -164,7 +167,7 @@ def test_room_class_is_set_properly_when_cached(self): class PromotionTestCase(TestCase): - @patch('gapipy.request.APIRequestor._request') + @mock.patch('gapipy.request.APIRequestor._request') def test_product_type_is_set_properly(self, mock_request): data = { 'products': [ diff --git a/tox.ini b/tox.ini index 125cbc4..29605e3 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,19 @@ [tox] -envlist = py27,py35,py36 +envlist = py{27,35,36,37,38,39} skip_missing_interpreters = True +[gh-actions] +# Note that this list of versions should correspond with what's in the GH workflow config +python = + 2.7: py27 + 3.5: py35 + 3.6: py36 + 3.7: py37 + 3.8: py38 + 3.9: py39 + [testenv] commands = pip install -r requirements-testing.txt - python setup.py test -passenv = GAPI_APPLICATION_KEY + nosetests {env:NOSE_ARGS:} +passenv = GAPI_APPLICATION_KEY NOSE_ARGS