TIL no.86 - Django - Kakao Social Login Test

박준규·2019년 11월 22일
7

TIL no.78 - Django - Kakao Social Login (Back End)에서 구현한 Kakao Social Login View의 Test에 대해 포스팅하겠습니다.

1. patch decorator route

Mocking하고자 하는 대상이 사용되는 곳을 경로로 잡아야 합니다.
이번 포스팅에서는 requests 모듈을 Mocking할 것이고
Mocking한 requests는 user앱의 views.py에서 사용될 것이므로
다음과 같이 경로를 설정해야합니다.

@patch(user.views.requests)

2. Client()

class Client(enforce_csrf_checks=False, json_encoder=DjangoJSONEncoder, **defaults)

instance를 생성할 때는 아무런 argument도 필요없습니다.
즉, c = Client()로 Client instance를 생성할 수 있습니다.

**defaults를 이용해 default header를 지정할 수 있습니다.

예를 들어, 다음과 같은 Client instance는
request마다 "User-Agent" header를 보냅니다.

c = Client(HTTP_USER_AGENT='Mozilla/5.0')

"HTTP_" prefix가 붙고
hyphen은 underscore로 해주고
UpperCase로 해줘야 하는데

이는 CGI specification를 따르기 때문입니다.

enforce_csrf_checks,json_encoder에 대한 설명은
Django docs Testing Tools를 참고해주세요.

3. Client.get()

get(path, data=None, follow=False, secure=False, **extra)

path에 GET request를 보내고 response를 return합니다.

GET data payload는 dictionary 형태로 넣어줍니다.

>>> c = Client()
>>> c.get('/customers/details/', {'name': 'fred', 'age': 7})

위 요청은 다음과 같습니다.

/customers/details/?name=fred&age=7

**extra keyword arguments parameter는
request에 담기는 header를 지정해줍니다.

>>> c = Client()
>>> c.get('/customers/details/', {'name': 'fred', 'age': 7},
...       HTTP_X_REQUESTED_WITH='XMLHttpRequest')

위 요청은 /customers/details/에 mapping된 view에
"HTTP_X_REQUESTED_WITH" header를 보냅니다.

또한 **extra를 통해 보내지는 header들은 CGI specification를 따라야 합니다.

예를 들어, "Host"라는 header를 따라하기 위해서는 "HTTP_HOST"로 보내야합니다.

4. tests.py

import jwt
import json
import unittest

from django.test   import Client, TestCase
from user.models   import User, SocialPlatform
from unittest.mock import patch, MagicMock

class KakaoSignInTest(TestCase): #Django의 TestCase를 사용

    def setUp(self):
        SocialPlatform.objects.create(platform_name='kakao')

    def tearDown(self):
        SocialPlatform.objects.get(platform_name='kakao').delete()        
    
    @patch('user.views.requests') #user 앱의 views.py에서 사용될 requests를 patch
    def test_kakao_sign_in(self, mocked_request):
        # 실제로 kakao API를 호출하지 않고
        # kakao API의 응답을 Fake로 작성
        class FakeResponse:
            def json(self):
                return {
                    "id" : 12345,
                    "properties" : {"nickname": "test_user"},
                    "kakao_account": {"email":"test@gmail.com"}
                }
        #test할 때, requests가 get 메서드로 받은 response는 FakeResponse의 instance
        mocked_request.get = MagicMock(return_value = FakeResponse())
        
        c = Client()
        header = {'HTTP_Authorization':'fake_token.1234'}
        # Client의 get method에 header담기
        # **extra는 keyword arguments이나 header는 dict이므로 **을 붙여준다.
        response = c.get('/user/kakao-login', content_type='applications/json', **header)
        self.assertEqual(response.status_code, 200)

if __name__ == '__main__':
    unittest.main()

코드에 관한 설명은 주석을 참고해주세요.

5. test 할 views.py

import jwt
import json
import requests

from django.views import View
from django.http  import JsonResponse

from .models            import SocialPlatform, User
from wemarpple.settings import SECRET_KEY

class KakaoLoginView(View):

    def get(self, request):
        try:
            access_token = request.headers.get('Authorization', None)
            url          = 'https://kapi.kakao.com/v2/user/me'
            headers      = {
                            'Authorization': f'Bearer {access_token}',
                            'Content-type' : 'application/x-www-form-urlencoded; charset=utf-8'
                           }
            
            #requests 자체를 mocking했으므로 실제로 kakao API를 호출하지 않음
            kakao_response = requests.get(url, headers = headers) #FakeResponse의 instance를 받음
            kakao_response = kakao_response.json() #FakeResponse instance의 json이라는 method의 return값을 받음
            kakao          = SocialPlatform.objects.get(platform_name = 'kakao')
            
            if User.objects.filter(social_platform = kakao, platform_id = kakao_response['id']).exists():

                user      = User.objects.get(social_platform = kakao, platform_id = kakao_response['id'])
                jwt_token = jwt.encode({'id':user.id}, SECRET_KEY, algorithm='HS256').decode('utf-8')
                
                return JsonResponse(
                                {
                                    'id'       : f'{user.id}',
                                    'name'     : f'{user.name}',
                                    'jwt_token': f'{jwt_token}',
                                },status = 200)

            user = User.objects.create(
                                    platform_id     = kakao_response['id'],
                                    name            = kakao_response['properties']['nickname'],
                                    social_platform = kakao,
                                    email           = kakao_response['kakao_account'].get('email', None)
                                    )
           
            jwt_token = jwt.encode({'id':user.id}, SECRET_KEY, algorithm='HS256').decode('utf-8')

            return JsonResponse(
                            {
                                'id'        : f'{user.id}',
                                'name'      : f'{user.name}',
                                'jwt_token' : f'{jwt_token}',
                            }, status = 200)
        except KeyError:
            return JsonResponse({'message':'WRONG_KEY'}, status = 400)

test가 어떻게 돌아가는지는 주석을 참고해주세요.

profile
devzunky@gmail.com

2개의 댓글

comment-user-thumbnail
2021년 1월 2일

매번 좋은 자료 감사합니다!

1개의 답글