Writing a first round of unit tests

Now, we will write a first round of unit tests. Specifically, we will write unit tests related to the user and message category resources: UserResource, UserListResource, CategoryResource, and CategoryListResource. Create a new tests sub-folder within the api folder. Then, create a new test_views.py file within the new api/tests sub-folder. Add the following lines, that declare many import statements and the first methods for the InitialTests class. The code file for the sample is included in the restful_python_chapter_08_01 folder:

from app import create_app 
from base64 import b64encode 
from flask import current_app, json, url_for 
from models import db, Category, Message, User 
import status 
from unittest import TestCase 
 
 
class InitialTests(TestCase): 
    def setUp(self): 
        self.app = create_app('test_config') 
        self.test_client = self.app.test_client() 
        self.app_context = self.app.app_context() 
        self.app_context.push() 
        self.test_user_name = 'testuser' 
        self.test_user_password = 'T3s!p4s5w0RDd12#' 
        db.create_all() 
 
    def tearDown(self): 
        db.session.remove() 
        db.drop_all() 
        self.app_context.pop() 
 
    def get_accept_content_type_headers(self): 
        return { 
            'Accept': 'application/json', 
            'Content-Type': 'application/json' 
        } 
 
    def get_authentication_headers(self, username, password): 
        authentication_headers = self.get_accept_content_type_headers() 
        authentication_headers['Authorization'] =  
            'Basic ' + b64encode((username + ':' + password).encode('utf-
             8')).decode('utf-8') 
        return authentication_headers 

The InitialTests class is a subclass of unittest.TestCase. The class overrides the setUp method that will be executed before each test method runs. The method calls the create_app function, declared in the app module, with 'test_config' as an argument. The function will set up a Flask app with this module as the configuration file, and therefore, the app will use the previously created configuration file that specifies the desired values for our testing database and environment. Then, the code sets the testing attribute for the recently created app to True in order for the exception to propagate to the test client.

The next line calls the self.app.test_client method to create a test client for the previously created Flask application and saves the test client in the test_client attribute. We will use the test client in our test methods to easily compose and send requests to our API. Then, the code saves and pushes the application context and creates two attributes with the user name and password we will use for our tests. Finally, the method calls the db.create_all method to create all the necessary tables in our test database configured in the test_config.py file.

The InitialTests class overrides the tearDown method that will be executed after each test method runs. The code removes the SQLAlchemy session, drops all the tables that we created in the test database before starting the execution of the tests, and pops the application context. This way, after each test finishes its execution, the test database will be empty again.

The get_accept_content_type_headers method builds and returns a dictionary (dict) with the values of the Accept and Content-Type header keys set to 'application/json'. We will call this method in our tests whenever we have to build a header to compose our requests without authentication.

The get_authentication_headers method calls the previously explained get_accept_content_type_headers method to generate the header key-value pairs without authentication. Then, the code adds the necessary value to the Authorization key with the appropriate encoding to provide the user name and password received in the username and password arguments. The last line returns the generated dictionary that includes authentication information. We will call this method in our tests whenever we have to build a header to compose our requests with authentication. We will use the user name and password we stored in attributes the setUp method.

Open the previously created test_views.py file within the new api/tests sub-folder. Add the following lines that declare many methods for the InitialTests class. The code file for the sample is included in the restful_python_chapter_08_01 folder.

    def test_request_without_authentication(self): 
        """ 
        Ensure we cannot access a resource that requirest authentication without an appropriate authentication header 
        """ 
        response = self.test_client.get( 
            url_for('api.messagelistresource', _external=True), 
            headers=self.get_accept_content_type_headers()) 
        self.assertTrue(response.status_code == status.HTTP_401_UNAUTHORIZED) 
 
    def create_user(self, name, password): 
        url = url_for('api.userlistresource', _external=True) 
        data = {'name': name, 'password': password} 
        response = self.test_client.post( 
            url,  
            headers=self.get_accept_content_type_headers(), 
            data=json.dumps(data)) 
        return response 
 
    def create_category(self, name): 
        url = url_for('api.categorylistresource', _external=True) 
        data = {'name': name} 
        response = self.test_client.post( 
            url,  
            headers=self.get_authentication_headers(self.test_user_name, 
            self.test_user_password), 
            data=json.dumps(data)) 
        return response 
 
    def test_create_and_retrieve_category(self): 
        """ 
        Ensure we can create a new Category and then retrieve it 
        """ 
        create_user_response = self.create_user(self.test_user_name,
        self.test_user_password) 
        self.assertEqual(create_user_response.status_code,
        status.HTTP_201_CREATED) 
        new_category_name = 'New Information' 
        post_response = self.create_category(new_category_name) 
        self.assertEqual(post_response.status_code, status.HTTP_201_CREATED) 
        self.assertEqual(Category.query.count(), 1) 
        post_response_data = json.loads(post_response.get_data(as_text=True)) 
        self.assertEqual(post_response_data['name'], new_category_name) 
        new_category_url = post_response_data['url'] 
        get_response = self.test_client.get( 
            new_category_url, 
            headers=self.get_authentication_headers(self.test_user_name,
            self.test_user_password)) 
        get_response_data = json.loads(get_response.get_data(as_text=True)) 
        self.assertEqual(get_response.status_code, status.HTTP_200_OK) 
        self.assertEqual(get_response_data['name'], new_category_name) 

The test_request_without_authentication method tests whether we have been rejected access to a resource that requires authentication when we don't provide an appropriate authentication header with the request. The method uses the test client to compose and send an HTTP GET request to the URL generated for the 'api.messagelistresource' resource to retrieve the list of messages. We need an authenticated request to retrieve the list of messages. However, the code calls the get_authentication_headers method to set the value for the headers argument in the call to self.test_client.get, and therefore, the code generates a request without authentication. Finally, the method uses assertTrue to check that the status_code for the response is HTTP 401 Unauthorized (status.HTTP_401_UNAUTHORIZED).

The create_user method uses the test client to compose and send an HTTP POST request to the URL generated for the 'api.userlistresource' resource to create a new user with the name and password received as arguments. We don't need an authenticated request to create a new user, and therefore, the code calls the previously explained get_accept_content_type_headers method to set the value for the headers argument in the call to self.test_client.post. Finally, the code returns the response from the POST request. Whenever we have to create an authenticated request, we will call the create_user method to create a new user.

The create_category method uses the test client to compose and send an HTTP POST request to the URL generated for the 'api.categorylistresource' resource to create a new Category with the name received as an argument. We need an authenticated request to create a new Category, and therefore, the code calls the previously explained get_authentication_headers method to set the value for the headers argument in the call to self.test_client.post. The user name and password are set to self.test_user_name and self.test_user_password. Finally, the code returns the response from the POST request. Whenever we have to create a category, we will call the create_category method after the appropriate user that authenticates the request has been created.

The test_create_and_retrieve_category method tests whether we can create a new Category and then retrieve it. The method calls the previously explained create_user method to create a new user and then use it to authenticate the HTTP POST request generated in the create_game_category method. Then, the code composes and sends an HTTP GET method to retrieve the recently created Category with the URL received in the response of the previous HTTP POST request. The method uses assertEqual to check for the following expected results:

  • The status_code for the HTTP POST response is HTTP 201 Created (status.HTTP_201_CREATED)
  • The total number of Category objects retrieved from the database is 1
  • The status_code for the HTTP GET response is HTTP 200 OK (status.HTTP_200_OK)
  • The value for the name key in the HTTP GET response is equal to the name specified for the new category

Open the previously created test_views.py file within the new api/tests sub-folder. Add the following lines that declare many methods for the InitialTests class. The code file for the sample is included in the restful_python_chapter_08_01 folder.

    def test_create_duplicated_category(self): 
        """ 
        Ensure we cannot create a duplicated Category 
        """ 
        create_user_response = self.create_user(self.test_user_name,
        self.test_user_password) 
        self.assertEqual(create_user_response.status_code,
        status.HTTP_201_CREATED) 
        new_category_name = 'New Information' 
        post_response = self.create_category(new_category_name) 
        self.assertEqual(post_response.status_code, status.HTTP_201_CREATED) 
        self.assertEqual(Category.query.count(), 1) 
        post_response_data = json.loads(post_response.get_data(as_text=True)) 
        self.assertEqual(post_response_data['name'], new_category_name) 
        second_post_response = self.create_category(new_category_name) 
        self.assertEqual(second_post_response.status_code,
        status.HTTP_400_BAD_REQUEST) 
        self.assertEqual(Category.query.count(), 1) 
 
    def test_retrieve_categories_list(self): 
        """ 
        Ensure we can retrieve the categories list 
        """ 
        create_user_response = self.create_user(self.test_user_name,
        self.test_user_password) 
        self.assertEqual(create_user_response.status_code,
        status.HTTP_201_CREATED) 
        new_category_name_1 = 'Error' 
        post_response_1 = self.create_category(new_category_name_1) 
        self.assertEqual(post_response_1.status_code, status.HTTP_201_CREATED) 
        new_category_name_2 = 'Warning' 
        post_response_2 = self.create_category(new_category_name_2) 
        self.assertEqual(post_response_2.status_code, status.HTTP_201_CREATED) 
        url = url_for('api.categorylistresource', _external=True) 
        get_response = self.test_client.get( 
            url, 
            headers=self.get_authentication_headers(self.test_user_name,
            self.test_user_password)) 
        get_response_data = json.loads(get_response.get_data(as_text=True)) 
        self.assertEqual(get_response.status_code, status.HTTP_200_OK) 
        self.assertEqual(len(get_response_data), 2) 
        self.assertEqual(get_response_data[0]['name'], new_category_name_1) 
        self.assertEqual(get_response_data[1]['name'], new_category_name_2) 
 
   
        """ 
        Ensure we can update the name for an existing category 
        """ 
        create_user_response = self.create_user(self.test_user_name,
        self.test_user_password) 
        self.assertEqual(create_user_response.status_code,
        status.HTTP_201_CREATED) 
        new_category_name_1 = 'Error 1' 
        post_response_1 = self.create_category(new_category_name_1) 
        self.assertEqual(post_response_1.status_code, status.HTTP_201_CREATED) 
        post_response_data_1 = json.loads(post_response_1.get_data(as_text=True)) 
        new_category_url = post_response_data_1['url'] 
        new_category_name_2 = 'Error 2' 
        data = {'name': new_category_name_2} 
        patch_response = self.test_client.patch( 
            new_category_url,  
            headers=self.get_authentication_headers(self.test_user_name,
            self.test_user_password), 
            data=json.dumps(data)) 
        self.assertEqual(patch_response.status_code, status.HTTP_200_OK) 
        get_response = self.test_client.get( 
            new_category_url, 
            headers=self.get_authentication_headers(self.test_user_name,
            self.test_user_password)) 
        get_response_data = json.loads(get_response.get_data(as_text=True)) 
        self.assertEqual(get_response.status_code, status.HTTP_200_OK) 
        self.assertEqual(get_response_data['name'], new_category_name_2)

The class declares the following methods whose name start with the test_ prefix:

  • test_create_duplicated_ category: Tests whether the unique constraints don't make it possible for us to create two categories with the same name or not. The second time we compose and send an HTTP POST request with a duplicate category name, we must receive an HTTP 400 Bad Request status code (status.HTTP_400_BAD_REQUEST) and the total number of Category objects retrieved from the database must be 1.
  • test_retrieve_categories_list: Tests whether we can retrieve the categories list or not. First, the method creates two categories and then it makes sure that the retrieved list includes the two created categories.
  • test_update_game_category: Tests whether we can update a single field for a category, specifically, its name field. The code makes sure that the name has been updated.

Tip

Note that each test that requires a specific condition in the database must execute all the necessary code for the database to be in this specific condition. For example, in order to update an existing category, first we must create a new category and then we can update it. Each test method will be executed without data from the previously executed test methods in the database, that is, each test will run with a database cleaned of data from previous tests.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.140.197.10