User Model and it's failure

I've started working on Users-API feature #22. Found that the Token-Authentication blocked api-docs - so I've created bug #39. I've temporarily commented out Token-Authentication and begun my work on User-API.

Then to my surprise ... User's API didn't properly handle bmi and the bmi-health-name.

So I've figure out it had to be some issue on Serializer site or on the QuerySet.

But no such luck, looking up this in internet lead me to some strange source-code changes that did nothing.

So I've dig a little deeper and debug the BMI algorithm itself. And voila! There was the problem!

As it turns out, yesterday I've made some testing for User model and for bmi-health-name function. While making that, I've used Mocking - which is in a general way a good idea. But to make use of Mocking you should be sure (having proper working tests!) that method/function you are mocking will output proper value. I didn't learn that lesson properly :)

So the BMI-Algorithm returned 0 all the time. When I've figure out where was the problem all other elements start ticking as they should !

As it turns out - I didn't learned lesson about making Unit-Tests independent from source-code - why ? Because of my laziness I've put some math-algorithm inside of test - instead of using hard-coded value.

When I've finally found it out- I've change code and then tests started to fail with wrong output as expected. I've fixed that :)

Now what about that User API ?

Well, when changing all of this things I've said before, I've changed the standard Serializer into a ModelSerializer. I also added Meta as described in ModelSerializer.

Then I've found that there is not tests for User Create/Update/Delete and retrieving list is not handled as it should.

So I've put effort into making best of this tests that are a coverage for User-API.

I also found bug #40 - when making tests for updating user.

Final result

Source Code of User API model:

class User(models.Model):
    """
    User model for obtaining personal information about biking riders
    """
    name = models.CharField(max_length=50)
    surname = models.CharField(max_length=100, default="")
    weight = models.IntegerField(default=0)
    height = models.IntegerField(default=0)

    def __unicode__(self):
        """
        Returns User information when using str/printing
        """
        return self.name

    def bmi(self):
        """
        Body Mass Index calculator simplified to number
        """
        return self.weight / ((self.height * self.height) / 10000)

    def bmi_health_name(self):
        """
        BMI Health Name - Returns proper naming for value of BMI
        """
        if self.bmi() < 0:
            return None
        if self.bmi() >= 0 and self.bmi() <= 18.5:
            return "Underweight"
        if self.bmi() > 18.5 and self.bmi() <= 24.9:
            return "Normal weight"
        if self.bmi() > 24.9 and self.bmi() <= 29.9:
            return "Overweight"
        if self.bmi() > 29.9:
            return "Obesity"

Source Code tests of User API:

"""
Tests for API
"""
import unittest
import json
from api.serializers import UserSerializer
from api.views import UserList, UserDetail
from web.models import User
from django.contrib.auth.models import User as AuthUser
from rest_framework.authtoken.models import Token
from rest_framework.test import APIClient
from rest_framework.test import APIRequestFactory
from rest_framework.test import force_authenticate


class APIGeneralTestCase(unittest.TestCase):
    "General Test Case for API"

    def setUp(self):
        self.path = "/api/user/"
        self.client = APIClient()
        self.user = AuthUser.objects.create_superuser('admin', 'admin@admin.com', 'admin123')
        self.token = Token.objects.get(user_id=self.user.id).key
        self.api = APIRequestFactory().get
        self.view = UserList.as_view()

    def tearDown(self):
        "Removes all AuthUser and Users objects at the end of each test"
        self.user.delete()
        User.objects.all().delete()

    def get_response_user_api(self, api_data=None, pk_id=None):
        " Creates response for /api/user List get with forcing login with Token-Authentication "
        self.client.force_login(user=self.user)
        request = self.api(
            self.path,
            api_data,
            HTTP_AUTHORIZATION='Token {}'.format(self.token),
            content_type='application/json'
        )
        force_authenticate(request)
        if pk_id is not None:
            return self.view(request, pk=pk_id)
        return self.view(request)

    def assert_status_code(self, response):
        " Asserts response status code with 200"
        self.assertEquals(response.status_code, 200)

    # pylint: disable=no-self-use
    def create_user_in_db(self):
        " Creates new user object in database "
        return User.objects.create(name='Bart', surname="Trab", weight=80, height=175)

class TestUserSerializer(unittest.TestCase):
    "UserSerializer tests"

    def test_user_serialize_to_json(self):
        "test if serializing to JSON works"

        mocked = User()
        mocked.name = "Anselmos"
        mocked.surname = "Somlesna"
        mocked.weight = 80
        mocked.height = 175

        user_serialized = UserSerializer(mocked)
        self.assertEqual(
            (user_serialized.data),
            {
                'height': 175,
                'surname': u'Somlesna',
                'id': None,
                'weight': 80,
                'name': u'Anselmos',
                'bmi': 26,
                'bmi_health_name': u'Overweight'
            }
        )


class TestUserList(APIGeneralTestCase):
    "UserList tests"

    def test_user_get_return_json(self):
        "test if using get returns json data"

        self.client.force_login(user=self.user)
        response = self.get_response_user_api()
        self.assert_status_code(response)

    def test_user_list_return_json_list(self):
        " Test if setting up user returns user list"

        input_user = self.create_user_in_db()

        self.client.force_login(user=self.user)
        response = self.get_response_user_api()
        self.assert_status_code(response)
        self.assertEquals(len(response.data), 1)
        self.assert_user_object(response.data[0], input_user)

    def test_get_two_user_list(self):
        " Test if setting up user returns user list"

        input_user = self.create_user_in_db()
        input_user2 = User.objects.create(name='B222art', surname="Trabaaaa", weight=50, height=100)

        response = self.get_response_user_api()
        self.assert_status_code(response)
        self.assertEquals(len(response.data), 2)
        self.assert_user_object(response.data[0], input_user)
        self.assert_user_object(response.data[1], input_user2)


    def assert_user_object(self, input_json, expected):
        "Asserts response User input json object == expected user model object. "
        self.assertEquals(input_json['name'], expected.name)
        self.assertEquals(input_json['surname'], expected.surname)
        self.assertEquals(input_json['weight'], expected.weight)
        self.assertEquals(input_json['height'], expected.height)
        self.assertEquals(input_json['bmi'], expected.bmi())
        self.assertEquals(input_json['bmi_health_name'], expected.bmi_health_name())


class TestPostUser(APIGeneralTestCase):
    "User Post tests"
    def setUp(self):
        super(self.__class__, self).setUp()
        self.api = APIRequestFactory().post

    def test_return_user_object(self):
        " Tests if making api request returns user object "

        data = json.dumps(
            {"name":"Test", "surname":"tester1", "weight":88, "height":173}
        )
        response = self.get_response_user_api(data)
        self.assertEquals(response.status_code, 201)


class TestDeleteUser(APIGeneralTestCase):
    "User Delete tests"
    def setUp(self):
        super(self.__class__, self).setUp()
        self.api = APIRequestFactory().delete
        self.view = UserDetail.as_view()

    def test_delete_user(self):
        " Tests if making api request deletes user with 204"

        user = self.create_user_in_db()
        self.path = "/api/user/{}/".format(user.id)
        response = self.get_response_user_api(pk_id=user.id)
        self.assertEquals(response.status_code, 204)


class TestUpdateUser(APIGeneralTestCase):
    "User Update tests"
    def setUp(self):
        super(self.__class__, self).setUp()
        self.api = APIRequestFactory().post
        self.view = UserList.as_view()

    def test_update_user(self):
        " Tests if making api request updates user with new name"

        user = self.create_user_in_db()
        self.path = "/api/user/{}/".format(user.id)
        response = self.get_response_user_api(
            json.dumps({"name": "newName", "weight": 88, "height": 111}),
            pk_id=user.id
        )
        self.assertEquals(response.status_code, 201)

Code commits done for this post:

Release:

13-05-17-user-api

Tools and applications used:

Acknowledgements of links I've found usefull while writing this article:

Others:

Django-Rest-Framework:

StackOverFlow:

Accomplished:

Issue #22 - User API

Final word

I wanted to make all of the issues that are there for 1st Milestone. Unfortunatelly I already see that it's impossible to make 6 big features in 4 hours (even less).

I'll move end time till Wednesday and let's see how much progress I will make.

Next time I'll just have to create less optimistic estimation of time per issue :)

That's all, thanks ! Keep in touch :)



Comments

comments powered by Disqus