kakao login test

Nam Eun-Ji·2020년 12월 14일

django unit test는 https://velog.io/@nameunzz/Unit-test 참고.

소셜로그인을 실행하려면 해당 소셜 사이트와의 api request와 response가 있어야하는데, unit test는 요청을 보내지 않고 테스트를 하므로 mock data로 테스트를 진행한다.

Mocking이란?

단위테스트를 진행하다보면 데이터베이스나 외부 API에 의존하는 코드를 테스트해야하는 경우가 생긴다. 이 때, 외부에 의존하는 코드를 가짜로 대체하는 기법을 Mocking이라 한다.

from unittest.mock import Mock, MagicMock, call

Mock

mock 객체의 리턴 값은 return_value를 통해 설정해줄 수 있다.

>>> from unittest.mock import Mock
>>> mock = Mock(return_value='Hello, Mock!')
>>> mock()
'Hello, Mock!'

MagicMock

파이썬에는 매직메서드라는 개념(ex. __str__)이 있는데 기본적으로 Mock 클래스를 사용하면 이러한 매직 메서드가 자동으로 모킹되지 않는다. 즉, 일일이 return_value를 할당해주어야하는 것이다.

from unittest.mock import Mock
mock = Mock()
mock.__str__ = Mock(return_value = "I'm a mock.")

하지만 MagicMock을 이용하면 이런 매직메서드는 알아서 모킹을 해놓기 때문에 편하다.

실제 프로젝트에서 단위 테스트를 작성할 때는 이렇게 직접 mock을 생성하는 것 보다는 patch() 데코레이터를 사용하는 것이 더 일반적이다.


@patch()

patch() 데코레이터는 특정 범위 내에서만 mocking이 가능하도록 해준다.

from unittest import TestCase, main
from unittest.mock import patch

def hello():
    return "Hello!"

class TestMe(TestCase):
    @patch("__main__.hello", return_value="Mock!")
    def test_hello(self, mock_hello):
        self.assertEqual(hello(), "Mock!")
        self.assertIs(hello, mock_hello)
        mock_hello.assert_called_once_with()

if __name__ == "__main__":
    main()

위 예제를 보면, 원래 "Hello!"을 리턴하는 hello() 함수가 "Mock!"을 대신 리턴하도록 @patch() 데코레이터로 patching을 하고 있다.

  • @patch() 데코레이터를 사용하여 patching을 하게되면 mock객체를 테스트 메서드의 인자로 추가하게 되는데, 위 예제 코드에서 mock_hello가 이 mock객체의 매개변수명으로 쓰이고 있다.
  • 첫번째 인자 : patching할 메서드를 package.module.Class.method 형태의 문자열로 받는다.



예제 - 카카오로그인테스트

views.py

class KakaoLoginView(View):
    # kakao_access_token으로 사용자 정보 받기 -> jwt(access_token) 발급
    def post(self, request):
        print('실제 1: ', request.__dict__)
        access_token = request.json()['access_token']
        url = 'https://kapi.kakao.com/v2/user/me'
        headers = {
            'Authorization': f'Bearer {access_token}',
            'Content-type': 'application/x-www-form-urlencoded;charset=utf-8'
        }
        response = requests.get(url, headers=headers)
        response = response.json()
        print('실제 2: ', response)
        if User.objects.filter(kakao=response['id']).exists():
            user = User.objects.get(kakao=response['id'])
        else:
            user = User.objects.create(kakao=response['id'], nickname=response['properties']['nickname'])

        access_token = jwt.encode({'user_id': user.id}, SECRET_KEY, algorithm='HS256')

        return JsonResponse({'access_token': access_token.decode('utf-8')}, status=200)

tests.py

class UserTest(TestCase):
    def setUp(self):
        pass

    def tearDown(self):
        User.objects.all().delete()

    @patch('user.views.requests')
    def test_kakao_signin_success(self, mocked_request):
        class KakaoResponse:
            def json(self):
                return {
                    'id': '1234',
                    'properties': {'nickname': 'test_nickname'}
                }

        client = Client()
        mocked_request.get = MagicMock(return_value=KakaoResponse())
        header = {'HTTP_Authorization': 'kakao_token'}
        response = client.get('/user', content_type='applications/json', **header)
        
        self.assertEqual(response.status_code, 200)
        
        token = response.json()['access_token']
        user_id = jwt.decode(token, SECRET_KEY, algorithm='HS256')['user_id']
        kakao_id = User.objects.get(id=user_id).kakao

        self.assertEqual(kakao_id, 1234)

@patch('user.views.requests')
실제 네트워크 연동이 일어나지 않게 하기 위해 post()함수 내에 존재하는 requests패키지의 get()함수를 patching해주어야 한다. 즉, requests.get()함수를 mock객체로 교환한 것이다.

mocked_request.get = MagicMock(return_value=KakaoResponse())
mock객체의 json 메서드의 리턴 객체를 KakaoResponse()로 지정해주었다. 그래서 self.assertEqual(kakao_id, 1234)를 했을 때 kakao_id가 1234로 나오게 된 것이다.


views.py에서 request와 response를 찍어보았다.

# request
실제 1: <MagicMock name='requests.post()' id='140720499876720'>

# response
실제 2: {'id': '1234', 'properties': {'nickname': 'test_nickname'}}

위에 설명한 것같이 똑같은 이야기이지만 @patch()를 통해 request가 MagicMock 클래스로 변경되고, MagicMock(return_value)를 통해 response가 임의로 지정해준 KakaoResponse.json() 리턴값으로 변경된 것을 확인할 수 있다.




참고
https://www.daleseo.com/python-unittest-mock/
https://www.daleseo.com/python-unittest-mock-patch/

profile
한 줄 소개가 자연스러워지는 그날까지

0개의 댓글