From 35ac2f4d2bfcfa6f2565634e30e282dcd042309d Mon Sep 17 00:00:00 2001 From: kimobrian Date: Sat, 14 Oct 2017 02:48:30 +0300 Subject: [PATCH 01/19] chore(test): Write first test --- users_test/__init__.py | 0 users_test/test_users.py | 14 ++++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 users_test/__init__.py create mode 100644 users_test/test_users.py diff --git a/users_test/__init__.py b/users_test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/users_test/test_users.py b/users_test/test_users.py new file mode 100644 index 0000000..53266f1 --- /dev/null +++ b/users_test/test_users.py @@ -0,0 +1,14 @@ +import unittest +from project.users import get_users + +class BasicTests(unittest.TestCase): + def test_request_response(self): + """ Send a request to the API server to retrieve users.""" + response = get_users() + + # Assert that the request-response cycle completed successfully. + self.assertEqual(response.status_code, 200) + + +if __name__ == "__main__": + unittest.main() From 772ce0ba5d23e5e69153b1eae001dfc3ee8f20e2 Mon Sep 17 00:00:00 2001 From: kimobrian Date: Sat, 21 Oct 2017 19:18:16 +0300 Subject: [PATCH 02/19] feat: Create users module --- __init__.py | 0 users.py | 10 ++++++++++ 2 files changed, 10 insertions(+) create mode 100644 __init__.py create mode 100644 users.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/users.py b/users.py new file mode 100644 index 0000000..9520287 --- /dev/null +++ b/users.py @@ -0,0 +1,10 @@ +import requests + +USERS_URL = 'http://jsonplaceholder.typicode.com/users' +def get_users(): + """Get list of users""" + response = requests.get(USERS_URL) + if response.ok: + return response + else: + return None From 106518bbfb81f0ee363debab8b3a1c2c94abcc30 Mon Sep 17 00:00:00 2001 From: kimobrian Date: Sat, 21 Oct 2017 19:47:29 +0300 Subject: [PATCH 03/19] chore: Mock using decorators --- users_test/test_users.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/users_test/test_users.py b/users_test/test_users.py index 53266f1..d39db40 100644 --- a/users_test/test_users.py +++ b/users_test/test_users.py @@ -1,9 +1,12 @@ import unittest from project.users import get_users +from unittest.mock import patch class BasicTests(unittest.TestCase): - def test_request_response(self): + @patch('project.users.requests.get') # Mock 'requests' module 'get' method. + def test_request_response(self, mock_get): """ Send a request to the API server to retrieve users.""" + mock_get.return_value.status_code = 200 # Mock status code of response. response = get_users() # Assert that the request-response cycle completed successfully. From 3dde70c174930e19b30a4ebe5e16249b07040fdf Mon Sep 17 00:00:00 2001 From: kimobrian Date: Sat, 21 Oct 2017 19:56:01 +0300 Subject: [PATCH 04/19] chore(context-manager): Mock using a context manager --- users_test/test_users.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/users_test/test_users.py b/users_test/test_users.py index d39db40..daf9307 100644 --- a/users_test/test_users.py +++ b/users_test/test_users.py @@ -3,11 +3,13 @@ from unittest.mock import patch class BasicTests(unittest.TestCase): - @patch('project.users.requests.get') # Mock 'requests' module 'get' method. - def test_request_response(self, mock_get): - """ Send a request to the API server to retrieve users.""" - mock_get.return_value.status_code = 200 # Mock status code of response. - response = get_users() + def test_request_response(self): + with patch('project.users.requests.get') as mock_get: + # Configure the mock to return a response with status code 200. + mock_get.return_value.status_code = 200 + + # Call the function, which will send a request to the server. + response = get_users() # Assert that the request-response cycle completed successfully. self.assertEqual(response.status_code, 200) From d2d5b428b9caf4d2ec8ba7b36fa287a1366d4bc6 Mon Sep 17 00:00:00 2001 From: kimobrian Date: Sat, 21 Oct 2017 19:59:54 +0300 Subject: [PATCH 05/19] chore(patcher): Mock using a patcher --- users_test/test_users.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/users_test/test_users.py b/users_test/test_users.py index daf9307..fb2fa9a 100644 --- a/users_test/test_users.py +++ b/users_test/test_users.py @@ -4,12 +4,19 @@ class BasicTests(unittest.TestCase): def test_request_response(self): - with patch('project.users.requests.get') as mock_get: - # Configure the mock to return a response with status code 200. - mock_get.return_value.status_code = 200 + mock_get_patcher = patch('project.users.requests.get') - # Call the function, which will send a request to the server. - response = get_users() + # Start patching 'requests.get'. + mock_get = mock_get_patcher.start() + + # Configure the mock to return a response with status code 200. + mock_get.return_value.status_code = 200 + + # Call the service, which will send a request to the server. + response = get_users() + + # Stop patching 'requests'. + mock_get_patcher.stop() # Assert that the request-response cycle completed successfully. self.assertEqual(response.status_code, 200) From cb4cbf31155dcf02e5f97b0ec3bba6d206f22f88 Mon Sep 17 00:00:00 2001 From: kimobrian Date: Tue, 24 Oct 2017 04:48:41 +0300 Subject: [PATCH 06/19] chore(whole-function): Mocking the whole function --- users_test/test_users.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/users_test/test_users.py b/users_test/test_users.py index fb2fa9a..4abbbbe 100644 --- a/users_test/test_users.py +++ b/users_test/test_users.py @@ -1,16 +1,23 @@ import unittest from project.users import get_users -from unittest.mock import patch +from unittest.mock import patch, Mock class BasicTests(unittest.TestCase): def test_request_response(self): mock_get_patcher = patch('project.users.requests.get') + users = [{ + "id": 0, + "first_name": "Dell", + "last_name": "Norval", + "phone": "994-979-3976" + }] # Start patching 'requests.get'. mock_get = mock_get_patcher.start() # Configure the mock to return a response with status code 200. - mock_get.return_value.status_code = 200 + mock_get.return_value = Mock(status_code = 200) + mock_get.return_value.json.return_value = users # Call the service, which will send a request to the server. response = get_users() @@ -20,6 +27,7 @@ def test_request_response(self): # Assert that the request-response cycle completed successfully. self.assertEqual(response.status_code, 200) + self.assertEqual(response.json(), users) if __name__ == "__main__": From 8ab84e8acd8929de96c370754a869a9047da3061 Mon Sep 17 00:00:00 2001 From: kimobrian Date: Thu, 2 Nov 2017 03:36:30 +0300 Subject: [PATCH 07/19] feat: Create method to fetch a single user --- users.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/users.py b/users.py index 9520287..a1dd5d6 100644 --- a/users.py +++ b/users.py @@ -1,6 +1,7 @@ import requests +import json -USERS_URL = 'http://jsonplaceholder.typicode.com/users' +USERS_URL = 'http://localhost:7800/customers' def get_users(): """Get list of users""" response = requests.get(USERS_URL) @@ -8,3 +9,11 @@ def get_users(): return response else: return None + + +def get_user(user_id): + """Get a single user using their ID""" + all_users = get_users().json() + for user in all_users: + if user['id'] == user_id: + return user From 01c5db890fb423b2945f925df7f90b33c0b70b68 Mon Sep 17 00:00:00 2001 From: kimobrian Date: Thu, 2 Nov 2017 03:37:33 +0300 Subject: [PATCH 08/19] feat: Create test for function to fetch single user --- users_test/test_users.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/users_test/test_users.py b/users_test/test_users.py index 4abbbbe..3ea1789 100644 --- a/users_test/test_users.py +++ b/users_test/test_users.py @@ -1,5 +1,5 @@ import unittest -from project.users import get_users +from project.users import get_users, get_user from unittest.mock import patch, Mock class BasicTests(unittest.TestCase): @@ -29,6 +29,18 @@ def test_request_response(self): self.assertEqual(response.status_code, 200) self.assertEqual(response.json(), users) + @patch('project.users.get_users') + def test_get_one_user(self, mock_get_users): + """Test for getting one user using their userID""" + users = [ + {'phone': '514-794-6957', 'first_name': 'Brant', 'last_name': 'Mekhi', 'id': 0}, + {'phone': '772-370-0117', 'first_name': 'Thalia', 'last_name': 'Kenyatta', 'id': 1}, + {'phone': '176-290-7637', 'first_name': 'Destin', 'last_name': 'Soledad', 'id': 2} + ] + mock_get_users.return_value = Mock() + mock_get_users.return_value.json.return_value = users + user = get_user(2) + self.assertEqual(user, users[2]) if __name__ == "__main__": unittest.main() From 579dcb62add8773faad9533e55345ea9f8cf4848 Mon Sep 17 00:00:00 2001 From: kimobrian Date: Thu, 2 Nov 2017 03:43:13 +0300 Subject: [PATCH 09/19] chore: Create requirements.txt --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..eb97054 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +nose2==0.6.5 +requests==2.18.4 From b91cdc5cd79062bcb7785cb0a4e28672c05785b4 Mon Sep 17 00:00:00 2001 From: kimobrian Date: Thu, 2 Nov 2017 03:51:09 +0300 Subject: [PATCH 10/19] chore: Create circle.yml file --- circle.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 circle.yml diff --git a/circle.yml b/circle.yml new file mode 100644 index 0000000..1d77dc4 --- /dev/null +++ b/circle.yml @@ -0,0 +1,11 @@ +machine: + python: + version: 3.5.2 + +dependencies: + override: + - pip install -r requirements.txt + +test: + override: + - nose2 --verbose From 0990062c67f9f9ba1b09cb66a0e03b107e49f279 Mon Sep 17 00:00:00 2001 From: kimobrian Date: Thu, 2 Nov 2017 04:02:30 +0300 Subject: [PATCH 11/19] chore: Refactor imports for CI --- users_test/test_users.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/users_test/test_users.py b/users_test/test_users.py index 3ea1789..6c44ffb 100644 --- a/users_test/test_users.py +++ b/users_test/test_users.py @@ -1,10 +1,10 @@ import unittest -from project.users import get_users, get_user +from users import get_users, get_user from unittest.mock import patch, Mock class BasicTests(unittest.TestCase): def test_request_response(self): - mock_get_patcher = patch('project.users.requests.get') + mock_get_patcher = patch('users.requests.get') users = [{ "id": 0, "first_name": "Dell", @@ -29,7 +29,7 @@ def test_request_response(self): self.assertEqual(response.status_code, 200) self.assertEqual(response.json(), users) - @patch('project.users.get_users') + @patch('users.get_users') def test_get_one_user(self, mock_get_users): """Test for getting one user using their userID""" users = [ From 8c04720c41b1b0a916e8a021eaa27c75206753d4 Mon Sep 17 00:00:00 2001 From: kimobrian Date: Thu, 2 Nov 2017 04:14:33 +0300 Subject: [PATCH 12/19] chore: test failing test --- users_test/test_users.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/users_test/test_users.py b/users_test/test_users.py index 6c44ffb..fb028e4 100644 --- a/users_test/test_users.py +++ b/users_test/test_users.py @@ -40,7 +40,7 @@ def test_get_one_user(self, mock_get_users): mock_get_users.return_value = Mock() mock_get_users.return_value.json.return_value = users user = get_user(2) - self.assertEqual(user, users[2]) + self.assertEqual(user, users[4]) if __name__ == "__main__": unittest.main() From 818089e6c40dc5697862d02c51f4e3d0593e45e9 Mon Sep 17 00:00:00 2001 From: kimobrian Date: Thu, 2 Nov 2017 04:18:01 +0300 Subject: [PATCH 13/19] chore: Fix failing test --- users_test/test_users.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/users_test/test_users.py b/users_test/test_users.py index fb028e4..6c44ffb 100644 --- a/users_test/test_users.py +++ b/users_test/test_users.py @@ -40,7 +40,7 @@ def test_get_one_user(self, mock_get_users): mock_get_users.return_value = Mock() mock_get_users.return_value.json.return_value = users user = get_user(2) - self.assertEqual(user, users[4]) + self.assertEqual(user, users[2]) if __name__ == "__main__": unittest.main() From 0f32cc874e0d4c460af3eafd1805659b6a7015c2 Mon Sep 17 00:00:00 2001 From: kimobrian Date: Thu, 2 Nov 2017 04:37:30 +0300 Subject: [PATCH 14/19] chore: Update README --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 2a4b628..a7ea1a8 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,14 @@ # Python-API-Testing Testing Python APIs + +## Sections. +- Introduction - Brief intro of the article and setup of the application. +- Test Driven Development(TDD) - A bit of explanation on TDD. +- Utilizing Mocks - Explain the concept of mocking and different ways of mocking as follows. + - Using Decorators + - Using a Context Manager + - Using a Patcher +- Mocking the whole function behavior - Demonstrate and explain how its done. +- Mocking third party functions - Demonstrate and explain how to mock out own functions/modules in other functions. +- Continuous integration with Circle CI - Brief walk through of how to combine testing and CI to make our development smooth, use case Github and Circle CI. +- Conclusion - Brief summary touching on mocking and CI. From 8d6d5816218f5a79b8bd306e98a3dbb6b614fd32 Mon Sep 17 00:00:00 2001 From: kimobrian Date: Wed, 15 Nov 2017 02:47:27 +0300 Subject: [PATCH 15/19] chore(url):Update URL for API --- users.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/users.py b/users.py index a1dd5d6..bc0733c 100644 --- a/users.py +++ b/users.py @@ -1,7 +1,7 @@ import requests import json -USERS_URL = 'http://localhost:7800/customers' +USERS_URL = 'http://jsonplaceholder.typicode.com/users' def get_users(): """Get list of users""" response = requests.get(USERS_URL) @@ -16,4 +16,4 @@ def get_user(user_id): all_users = get_users().json() for user in all_users: if user['id'] == user_id: - return user + return user \ No newline at end of file From 500c8f514f5afa1bd433aa39c68eb25cac8be429 Mon Sep 17 00:00:00 2001 From: kimobrian Date: Wed, 22 Nov 2017 01:45:52 +0300 Subject: [PATCH 16/19] chore(pep-8): Use PEP8 styling --- users.py | 4 +++- users_test/test_users.py | 15 ++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/users.py b/users.py index bc0733c..68c0e37 100644 --- a/users.py +++ b/users.py @@ -2,6 +2,8 @@ import json USERS_URL = 'http://jsonplaceholder.typicode.com/users' + + def get_users(): """Get list of users""" response = requests.get(USERS_URL) @@ -16,4 +18,4 @@ def get_user(user_id): all_users = get_users().json() for user in all_users: if user['id'] == user_id: - return user \ No newline at end of file + return user diff --git a/users_test/test_users.py b/users_test/test_users.py index 6c44ffb..276b32a 100644 --- a/users_test/test_users.py +++ b/users_test/test_users.py @@ -2,6 +2,7 @@ from users import get_users, get_user from unittest.mock import patch, Mock + class BasicTests(unittest.TestCase): def test_request_response(self): mock_get_patcher = patch('users.requests.get') @@ -16,7 +17,7 @@ def test_request_response(self): mock_get = mock_get_patcher.start() # Configure the mock to return a response with status code 200. - mock_get.return_value = Mock(status_code = 200) + mock_get.return_value = Mock(status_code=200) mock_get.return_value.json.return_value = users # Call the service, which will send a request to the server. @@ -33,14 +34,18 @@ def test_request_response(self): def test_get_one_user(self, mock_get_users): """Test for getting one user using their userID""" users = [ - {'phone': '514-794-6957', 'first_name': 'Brant', 'last_name': 'Mekhi', 'id': 0}, - {'phone': '772-370-0117', 'first_name': 'Thalia', 'last_name': 'Kenyatta', 'id': 1}, - {'phone': '176-290-7637', 'first_name': 'Destin', 'last_name': 'Soledad', 'id': 2} - ] + {'phone': '514-794-6957', 'first_name': 'Brant', + 'last_name': 'Mekhi', 'id': 0}, + {'phone': '772-370-0117', 'first_name': 'Thalia', + 'last_name': 'Kenyatta', 'id': 1}, + {'phone': '176-290-7637', 'first_name': 'Destin', + 'last_name': 'Soledad', 'id': 2} + ] mock_get_users.return_value = Mock() mock_get_users.return_value.json.return_value = users user = get_user(2) self.assertEqual(user, users[2]) + if __name__ == "__main__": unittest.main() From 0d8f4dc7f7679a960edbc9ed75d7d6cd2960c30d Mon Sep 17 00:00:00 2001 From: kimobrian Date: Wed, 22 Nov 2017 03:46:04 +0300 Subject: [PATCH 17/19] chore(refactor): Include all tests with more comments --- users_test/test_users.py | 56 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/users_test/test_users.py b/users_test/test_users.py index 276b32a..0692d0a 100644 --- a/users_test/test_users.py +++ b/users_test/test_users.py @@ -4,7 +4,29 @@ class BasicTests(unittest.TestCase): - def test_request_response(self): + @patch('users.requests.get') # Mock 'requests' module 'get' method. + def test_request_response_with_decorator(self, mock_get): + """Mocking using a decorator""" + mock_get.return_value.status_code = 200 # Mock status code of response. + response = get_users() + + # Assert that the request-response cycle completed successfully. + self.assertEqual(response.status_code, 200) + + def test_request_response_with_context_manager(self): + """Mocking using a context manager""" + with patch('users.requests.get') as mock_get: + # Configure the mock to return a response with status code 200. + mock_get.return_value.status_code = 200 + + # Call the function, which will send a request to the server. + response = get_users() + + # Assert that the request-response cycle completed successfully. + self.assertEqual(response.status_code, 200) + + def test_request_response_with_patcher(self): + """Mocking using a patcher""" mock_get_patcher = patch('users.requests.get') users = [{ "id": 0, @@ -29,10 +51,40 @@ def test_request_response(self): # Assert that the request-response cycle completed successfully. self.assertEqual(response.status_code, 200) self.assertEqual(response.json(), users) + + def test_mock_whole_function(self): + """Mocking a whole function""" + mock_get_patcher = patch('users.requests.get') + users = [{ + "id": 0, + "first_name": "Dell", + "last_name": "Norval", + "phone": "994-979-3976" + }] + + # Start patching 'requests.get'. + mock_get = mock_get_patcher.start() + + # Configure the mock to return a response with status code 200 and a list of users. + mock_get.return_value = Mock(status_code = 200) + mock_get.return_value.json.return_value = users + + # Call the service, which will send a request to the server. + response = get_users() + + # Stop patching 'requests'. + mock_get_patcher.stop() + + # Assert that the request-response cycle completed successfully. + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json(), users) @patch('users.get_users') def test_get_one_user(self, mock_get_users): - """Test for getting one user using their userID""" + """ + Test for getting one user using their userID + Demonstrates mocking third party functions + """ users = [ {'phone': '514-794-6957', 'first_name': 'Brant', 'last_name': 'Mekhi', 'id': 0}, From 97463d4645c56b3a7fad9990d9d2a638a2295518 Mon Sep 17 00:00:00 2001 From: kimobrian Date: Wed, 22 Nov 2017 04:28:09 +0300 Subject: [PATCH 18/19] chore(setup): Update README witn the setup info --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index a7ea1a8..4132987 100644 --- a/README.md +++ b/README.md @@ -12,3 +12,12 @@ Testing Python APIs - Mocking third party functions - Demonstrate and explain how to mock out own functions/modules in other functions. - Continuous integration with Circle CI - Brief walk through of how to combine testing and CI to make our development smooth, use case Github and Circle CI. - Conclusion - Brief summary touching on mocking and CI. + +## Setup. + ```sh +$ git clone git@github.com:kimobrian/Python-API-Testing.git +$ cd Python-API-Testing +$ virtualenv -p python3 venv # Create virtual environment +$ source venv/bin/activate # Activate virtual environment +$ pip install -r requirements.txt + ``` From db1294bfa265f9ec25e08d38bd670bc0a1b4da17 Mon Sep 17 00:00:00 2001 From: O'Brian Kimokoti Date: Fri, 22 Dec 2017 18:04:12 +0300 Subject: [PATCH 19/19] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4132987..d717648 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Testing Python APIs - Using a Patcher - Mocking the whole function behavior - Demonstrate and explain how its done. - Mocking third party functions - Demonstrate and explain how to mock out own functions/modules in other functions. -- Continuous integration with Circle CI - Brief walk through of how to combine testing and CI to make our development smooth, use case Github and Circle CI. +- ~Continuous integration with Circle CI - Brief walk through of how to combine testing and CI to make our development smooth, use case Github and Circle CI.~ - Conclusion - Brief summary touching on mocking and CI. ## Setup.