From 04a16e76c5cff62051215bbcfcb6dca4d7bb6b2f Mon Sep 17 00:00:00 2001 From: EliuX Date: Fri, 13 Mar 2020 18:02:01 -0500 Subject: [PATCH 1/2] Closes #23 Add mocking support --- README.md | 2 +- requirements/dev.txt | 5 ++++- tests/conftest.py | 11 +++++++---- tests/time_entries/time_enties_namespace_test.py | 9 ++++++++- time_tracker_api/__init__.py | 5 +++-- time_tracker_api/config.py | 3 +++ time_tracker_api/database.py | 2 +- 7 files changed, 27 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 1e149522..ea331484 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ We are using Pytest](https://docs.pytest.org/en/latest/index.html) for tests. Th To run the tests just execute: -``` +```bash python3 -m pytest -v ``` diff --git a/requirements/dev.txt b/requirements/dev.txt index e7f629d0..7c12fdab 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -5,7 +5,10 @@ # For development # Tests -pytest==4.1.1 +pytest==5.2.0 + +# Mocking +pytest-mock==2.0.0 # Coverage coverage==4.5.1 \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py index dab7eecd..93a3b54d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,15 +1,18 @@ - import pytest +from flask import Flask +from flask.testing import FlaskClient from time_tracker_api import create_app + @pytest.fixture(scope='session') -def app(): +def app() -> Flask: """An instance of the app for tests""" return create_app() + @pytest.fixture -def client(app): +def client(app: Flask) -> FlaskClient: """A test client for the app.""" with app.test_client() as c: - return c \ No newline at end of file + return c diff --git a/tests/time_entries/time_enties_namespace_test.py b/tests/time_entries/time_enties_namespace_test.py index 6d542456..1111309e 100644 --- a/tests/time_entries/time_enties_namespace_test.py +++ b/tests/time_entries/time_enties_namespace_test.py @@ -1,10 +1,17 @@ from flask import json +from flask.testing import FlaskClient +from pytest_mock import MockFixture -def test_list_should_return_empty_array(client): + +def test_list_should_return_empty_array(mocker: MockFixture, client: FlaskClient): + from time_tracker_api.time_entries.time_entries_namespace import model """Should return an empty array""" + model_mock = mocker.patch.object(model, 'find_all', return_value=[]) + response = client.get("/time-entries", follow_redirects=True) assert 200 == response.status_code json_data = json.loads(response.data) assert [] == json_data + model_mock.assert_called_once() diff --git a/time_tracker_api/__init__.py b/time_tracker_api/__init__.py index c1a26a1a..a37e669c 100644 --- a/time_tracker_api/__init__.py +++ b/time_tracker_api/__init__.py @@ -1,8 +1,9 @@ import os + from flask import Flask -def create_app(config_path='time_tracker_api.config.WhateverDevelopConfig', +def create_app(config_path='time_tracker_api.config.DefaultConfig', config_data=None): flask_app = Flask(__name__) @@ -12,7 +13,7 @@ def create_app(config_path='time_tracker_api.config.WhateverDevelopConfig', return flask_app -def init_app_config(app, config_path, config_data=None): +def init_app_config(app: Flask, config_path: str, config_data: dict = None): if config_path: app.config.from_object(config_path) else: diff --git a/time_tracker_api/config.py b/time_tracker_api/config.py index 7d6fab4b..8caec231 100644 --- a/time_tracker_api/config.py +++ b/time_tracker_api/config.py @@ -7,3 +7,6 @@ class WhateverDevelopConfig(Config): FLASK_DEBUG = True FLASK_ENV = "develop" DATABASE = "whatever" + + +DefaultConfig = WhateverDevelopConfig diff --git a/time_tracker_api/database.py b/time_tracker_api/database.py index 45ec8093..80e297da 100644 --- a/time_tracker_api/database.py +++ b/time_tracker_api/database.py @@ -25,4 +25,4 @@ def create(model_name: str): def use_whatever(): from time_tracker_api import whatever_repository - return whatever_repository \ No newline at end of file + return whatever_repository From ad773897c0f1545b1990f060b63356fa8bcf6be8 Mon Sep 17 00:00:00 2001 From: EliuX Date: Mon, 16 Mar 2020 14:06:49 -0500 Subject: [PATCH 2/2] Rename API models --- .../activities/activities_namespace.py | 20 +++++++++---------- .../projects/projects_namespace.py | 20 +++++++++---------- .../technologies/technologies_namespace.py | 20 +++++++++---------- .../time_entries/time_entries_namespace.py | 20 +++++++++---------- 4 files changed, 40 insertions(+), 40 deletions(-) diff --git a/time_tracker_api/activities/activities_namespace.py b/time_tracker_api/activities/activities_namespace.py index b7c5db63..84517322 100644 --- a/time_tracker_api/activities/activities_namespace.py +++ b/time_tracker_api/activities/activities_namespace.py @@ -4,7 +4,7 @@ ns = Namespace('activities', description='API for activities') # Activity Model -activity = ns.model('Activity', { +activity_input = ns.model('ActivityInput', { 'name': fields.String( required=True, title='Name', @@ -27,9 +27,9 @@ } activity_response_fields.update(audit_fields) -activity_response = ns.inherit( - 'ActivityResponse', - activity, +activity = ns.inherit( + 'Activity', + activity_input, activity_response_fields ) @@ -37,14 +37,14 @@ @ns.route('') class Activities(Resource): @ns.doc('list_activities') - @ns.marshal_list_with(activity_response, code=200) + @ns.marshal_list_with(activity, code=200) def get(self): """List all available activities""" return [] @ns.doc('create_activity') - @ns.expect(activity) - @ns.marshal_with(activity_response, code=201) + @ns.expect(activity_input) + @ns.marshal_with(activity, code=201) @ns.response(400, 'Invalid format of the attributes of the activity.') def post(self): """Create a single activity""" @@ -56,7 +56,7 @@ def post(self): @ns.param('id', 'The unique identifier of the activity') class Activity(Resource): @ns.doc('get_activity') - @ns.marshal_with(activity_response) + @ns.marshal_with(activity) def get(self, id): """Retrieve all the data of a single activity""" return {} @@ -69,8 +69,8 @@ def delete(self, id): @ns.doc('put_activity') @ns.response(400, 'Invalid format of the attributes of the activity.') - @ns.expect(activity) - @ns.marshal_with(activity_response) + @ns.expect(activity_input) + @ns.marshal_with(activity) def put(self, id): """Updates an activity""" return ns.payload diff --git a/time_tracker_api/projects/projects_namespace.py b/time_tracker_api/projects/projects_namespace.py index 914ceaab..52756e9b 100644 --- a/time_tracker_api/projects/projects_namespace.py +++ b/time_tracker_api/projects/projects_namespace.py @@ -6,7 +6,7 @@ ns = Namespace('projects', description='API for projects (clients)') # Project Model -project = ns.model('Project', { +project_input = ns.model('ProjectInput', { 'name': fields.String( required=True, title='Name', @@ -35,9 +35,9 @@ } project_response_fields.update(audit_fields) -project_response = ns.inherit( - 'ProjectResponse', - project, +project = ns.inherit( + 'Project', + project_input, project_response_fields ) @@ -45,14 +45,14 @@ @ns.route('') class Projects(Resource): @ns.doc('list_projects') - @ns.marshal_list_with(project_response, code=200) + @ns.marshal_list_with(project, code=200) def get(self): """List all projects""" return project_dao.get_all(), 200 @ns.doc('create_project') - @ns.expect(project) - @ns.marshal_with(project_response, code=201) + @ns.expect(project_input) + @ns.marshal_with(project, code=201) def post(self): """Create a project""" return project_dao.create(ns.payload), 201 @@ -71,7 +71,7 @@ def post(self): @ns.param('uid', 'The project identifier') class Project(Resource): @ns.doc('get_project') - @ns.marshal_with(project_response) + @ns.marshal_with(project) def get(self, uid): """Retrieve a project""" return project_dao.get(uid) @@ -90,8 +90,8 @@ def post(self, uid): abort(message=str(e), code=404) @ns.doc('put_project') - @ns.expect(project) - @ns.marshal_with(project_response) + @ns.expect(project_input) + @ns.marshal_with(project) def put(self, uid): """Create or replace a project""" return project_dao.update(uid, ns.payload) diff --git a/time_tracker_api/technologies/technologies_namespace.py b/time_tracker_api/technologies/technologies_namespace.py index 9661592f..c15e7425 100644 --- a/time_tracker_api/technologies/technologies_namespace.py +++ b/time_tracker_api/technologies/technologies_namespace.py @@ -4,7 +4,7 @@ ns = Namespace('technologies', description='API for technologies used') # Technology Model -technology = ns.model('Technology', { +technology_input = ns.model('TechnologyInput', { 'name': fields.String( required=True, title='Name', @@ -23,9 +23,9 @@ } technology_response_fields.update(audit_fields) -technology_response = ns.inherit( - 'TechnologyResponse', - technology, +technology = ns.inherit( + 'Technology', + technology_input, technology_response_fields ) @@ -33,14 +33,14 @@ @ns.route('') class Technologies(Resource): @ns.doc('list_technologies') - @ns.marshal_list_with(technology_response, code=200) + @ns.marshal_list_with(technology, code=200) def get(self): """List all technologies""" return [], 200 @ns.doc('create_technology') - @ns.expect(technology) - @ns.marshal_with(technology_response, code=201) + @ns.expect(technology_input) + @ns.marshal_with(technology, code=201) def post(self): """Create a technology""" return ns.payload, 201 @@ -51,14 +51,14 @@ def post(self): @ns.param('uid', 'The technology identifier') class Technology(Resource): @ns.doc('get_technology') - @ns.marshal_with(technology_response) + @ns.marshal_with(technology) def get(self, uid): """Retrieve a technology""" return {} @ns.doc('put_technology') - @ns.expect(technology) - @ns.marshal_with(technology_response) + @ns.expect(technology_input) + @ns.marshal_with(technology) def put(self, uid): """Updates a technology""" return ns.payload() diff --git a/time_tracker_api/time_entries/time_entries_namespace.py b/time_tracker_api/time_entries/time_entries_namespace.py index 59c94bc9..eab41e8a 100644 --- a/time_tracker_api/time_entries/time_entries_namespace.py +++ b/time_tracker_api/time_entries/time_entries_namespace.py @@ -5,7 +5,7 @@ ns = Namespace('time-entries', description='API for time entries') # TimeEntry Model -time_entry = ns.model('TimeEntry', { +time_entry_input = ns.model('TimeEntryInput', { 'project_id': fields.String( required=True, title='Project', @@ -55,9 +55,9 @@ } time_entry_response_fields.update(audit_fields) -time_entry_response = ns.inherit( - 'TimeEntryResponse', - time_entry, +time_entry = ns.inherit( + 'TimeEntry', + time_entry_input, time_entry_response_fields, ) @@ -67,14 +67,14 @@ @ns.route('') class TimeEntries(Resource): @ns.doc('list_time_entries') - @ns.marshal_list_with(time_entry_response, code=200) + @ns.marshal_list_with(time_entry, code=200) def get(self): """List all available time entries""" return model.find_all() @ns.doc('create_time_entry') - @ns.expect(time_entry) - @ns.marshal_with(time_entry_response, code=201) + @ns.expect(time_entry_input) + @ns.marshal_with(time_entry, code=201) @ns.response(400, 'Invalid format of the attributes of the time entry') def post(self): """Starts a time entry by creating it""" @@ -86,7 +86,7 @@ def post(self): @ns.param('id', 'The unique identifier of the time entry') class TimeEntry(Resource): @ns.doc('get_time_entry') - @ns.marshal_with(time_entry_response) + @ns.marshal_with(time_entry) def get(self, id): """Retrieve all the data of a single time entry""" return {} @@ -99,8 +99,8 @@ def delete(self, id): @ns.doc('put_time_entry') @ns.response(400, 'Invalid format of the attributes of the time entry.') - @ns.expect(time_entry) - @ns.marshal_with(time_entry_response) + @ns.expect(time_entry_input) + @ns.marshal_with(time_entry) def put(self, id): """Updates a time entry""" return ns.payload