【问题标题】:Unittest Django: Mock external API, what is proper way?Unittest Django:模拟外部API,什么是正确的方法?
【发布时间】:2018-10-13 21:57:57
【问题描述】:

我在理解 mock 的工作原理以及如何使用 mock 对象编写单元测试时遇到了问题。每次我的模型调用 save() 方法时,我都想模拟一个外部 api 调用。 我的代码: models.py

from . import utils

class Book(Titleable, Isactiveable, Timestampable, IsVoidable, models.Model):
   title
   orig_author
   orig_title
   isbn 

    def save(self, *args, **kwargs):
        if self.isbn:
            google_data = utils.get_original_title_and_name(self.isbn)
            if google_data:
                self.original_author = google_data['author']
                self.original_title = google_data['title']
        super().save(*args, **kwargs)

utils.py

def get_original_title_and_name(isbn, **kawargs):
    isbn_search_string = 'isbn:{}'.format(isbn)
    payload = {
        'key': GOOGLE_API_KEY,
        'q': isbn_search_string,
        'printType': 'books',
    }
    r = requests.get(GOOGLE_API_URL, params=payload)
    response = r.json()
    if 'items' in response.keys():
        title = response['items'][THE_FIRST_INDEX]['volumeInfo']['title']
        author = response['items'][THE_FIRST_INDEX]['volumeInfo']['authors'][THE_FIRST_INDEX]

        return {
            'title': title,
            'author': author
        }
    else:
        return None

我开始阅读文档并编写测试:

test.py:

from unittest import mock
from django.test import TestCase
from rest_framework import status
from .constants import THE_FIRST_INDEX, GOOGLE_API_URL, GOOGLE_API_KEY

class BookModelTestCase(TestCase):
    @mock.patch('requests.get')
    def test_get_original_title_and_name_from_google_api(self, mock_get):
        # Define new Mock object
        mock_response = mock.Mock()
        # Define response data from Google API
        expected_dict = {
            'kind': 'books#volumes',
            'totalItems': 1,
            'items': [
                {
                    'kind': 'books#volume',
                    'id': 'IHxXBAAAQBAJ',
                    'etag': 'B3N9X8vAMWg',
                    'selfLink': 'https://www.googleapis.com/books/v1/volumes/IHxXBAAAQBAJ',
                    'volumeInfo': {
                        'title': "Alice's Adventures in Wonderland",
                        'authors': [
                            'Lewis Carroll'
                        ]
                    }
                }
                    ]
            }

        # Define response data for my Mock object
        mock_response.json.return_value = expected_dict
        mock_response.status_code = 200

        # Define response for the fake API
        mock_get.return_value = mock_response

首先,我不能为@mock.patch 正确写target。如果将target 定义为utuls.get_original_title_and_name.requests.get,我得到ModuleNotFoundError。我也无法理解如何对外部 API 进行假调用并验证接收到的数据(如果我已经定义了mock_response.json.return_value = expected_dict?)并验证我的 save() 方法是否正常工作?

如何为这种情况编写测试?谁能解释一下这个案例?

【问题讨论】:

  • 完全不相关,但我绝对不会在 model.save() 中进行 API 调用。我宁愿为这种东西使用异步队列。
  • 你在正确的轨道上,但不是修补utils.get_original_title_and_name.requests.get,而是修补utils.requests.get
  • 你写的是ModuleNotFoundErrorutuls.get_original_title_and_name.requests.get (utuls -> utils) 中是否有错字

标签: python django unit-testing mocking


【解决方案1】:

您应该模拟被测代码的直接合作者。对于Book,那就是utils。对于utils,那就是requests

所以对于BookModelTestCase

class BookModelTestCase(TestCase):

    @mock.patch('app.models.utils')
    def test_save_book_calls_google_api(self, mock_utils):
        mock_utils.get_original_title_and_name.return_value = {
            'title': 'Google title',
            'author': 'Google author'
        }

        book = Book(
            title='Some title',
            isbn='12345'
        )
        book.save()

        self.assertEqual(book.title, 'Google title')
        self.assertEqual(book.author, 'Google author')
        mock_utils.get_original_title_and_name.assert_called_once_with('12345')

然后你可以创建一个单独的测试用例来测试get_original_title_and_name

class GetOriginalTitleAndNameTestCase(TestCase):

    @mock.patch('app.utils.requests.get')
    def test_get_original_title_and_name_from_google_api(self, mock_get):
        mock_response = mock.Mock()
        # Define response data from Google API
        expected_dict = {
            'kind': 'books#volumes',
            'totalItems': 1,
            'items': [
                {
                    'kind': 'books#volume',
                    'id': 'IHxXBAAAQBAJ',
                    'etag': 'B3N9X8vAMWg',
                    'selfLink': 'https://www.googleapis.com/books/v1/volumes/IHxXBAAAQBAJ',
                    'volumeInfo': {
                        'title': "Alice's Adventures in Wonderland",
                        'authors': [
                            'Lewis Carroll'
                        ]
                    }
                }
                    ]
            }

        # Define response data for my Mock object
        mock_response.json.return_value = expected_dict
        mock_response.status_code = 200

        # Define response for the fake API
        mock_get.return_value = mock_response

        # Call the function
        result = get_original_title_and_name(12345)

        self.assertEqual(result, {
            'title': "Alice's Adventures in Wonderland", 
            'author': 'Lewis Carroll'
        })
        mock_get.assert_called_once_with(GOOGLE_API_URL, params={
            'key': GOOGLE_API_KEY,
            'q': 'isbn:12345',
            'printType': 'books',
        })

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-05-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-08
    • 2014-10-04
    相关资源
    最近更新 更多