CSRF令牌干扰TDD - 是否有一个变量存储CSRF输出?

5

因此,在使用Django进行比较带有表单输入的预期和实际html时,我一直返回失败的测试。后来,我打印出结果并意识到差异是由我的{% csrf_token %}引起的,代码如下:

<input type='hidden' name='csrfmiddlewaretoken' value='hrPLKVOlhAIXmxcHI4XaFjqgEAMCTfUa' />

所以,我希望得到一个简单的答案,但是我一直没有找到: 如何渲染csrf_token的结果以便在测试中使用?
以下是测试设置和失败信息:
def test_home_page_returns_correct_html_with_POST(self):
        request = HttpRequest()
        request.method = 'POST'
        request.POST['item_text'] = 'A new list item'

        response = home_page(request)

        self.assertIn('A new list item', response.content.decode())

        expected_html = render_to_string(
        'home.html',
        {'new_item_text': 'A new list item'},
******this is where I'm hoping for a simple one-line mapping******

    )
    self.assertEqual(response.content.decode(), expected_html)

以下为views.py中的渲染结果:

def home_page(request):
    return render(request, 'home.html', {
        'new_item_text': request.POST.get('item_text'),
    })

以下是测试失败的信息,当我使用python manage.py test运行测试时:

FAIL: test_home_page_returns_correct_html_with_POST (lists.tests.HomePageTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Users\Me\PycharmProjects\superlists\lists\tests.py", line 29, in test_home_page_returns_correct_html_with_POST
    self.assertEqual(response.content.decode(), expected_html)
AssertionError: '<!DO[298 chars]     <input type=\'hidden\' name=\'csrfmiddlew[179 chars]tml>' != '<!DO[298 chars]     \n    </form>\n\n    <table
 id="id_list_t[82 chars]tml>'

----------------------------------------------------------------------

2
你能展示更多的测试代码吗?如果你正在使用内置的Django测试客户端,这应该不是问题。根据文档,“默认情况下,测试客户端将禁用站点执行的任何CSRF检查。” - Joey Wilhelm
我已经更新了问题,包括错误、设置等等。我会检查刚刚发布的答案,看看它的表现如何。谢谢。 - Jordon Birk
5个回答

5

3

如果可以的话,我想提出一种更好的方法来执行这个测试,使用内置的Django测试客户端。它将为您处理所有CSRF检查,并且更易于使用。它可能看起来像这样:

def test_home_page_returns_correct_html_with_POST(self):
    url = reverse('your_home_page_view_url_name')
    response = self.client.post(url, {'item_text': 'A new list item'})
    self.assertContains(response, 'A new list item')

注意,这里也使用了 assertContains,它是 Django 测试套件 提供的断言 之一。

2
我非常喜欢这种方法。而且,为了节省时间,对于任何未来阅读此问题的读者, reverse()需要导入语句:from django.core.urlresolvers import reverse - Jordon Birk

2
CSRF令牌是模板上下文数据的一部分,如果您正在使用Django TestCase类,则可以使用该数据。
response = self.client.get(url)
print(response.context)

https://docs.djangoproject.com/en/1.9/topics/testing/tools/#django.test.Response

这里的关键是csrf_token

https://docs.djangoproject.com/en/1.9/_modules/django/template/context_processors/

编辑: 因为您问如何比较测试中呈现的HTML和测试服务器输出的结果:

因为您在模板中使用了{% csrf_token %},所以不能将CSRF令牌从响应上下文提供给render_to_string方法以使其使用相同的值。相反,您需要在render_to_string的结果中替换它,也许先使用selenium查找输入元素(这本身就是一个测试)。但是,这个测试的实用性是有问题的。它只会帮助确保存在CSRF令牌,但在常规工作模式下,服务器已经检查过了。

基本上,您应该测试您直接影响的任何代码,而不是Django魔法提供的任何内容。例如,如果您正在执行自定义表单验证,则应测试该验证,而不是由Django提供的任何验证。如果您在ListViews中更改查询集(自定义筛选等)或在DetailViews中使用get_object(),则应检查结果列表和404错误是否根据您的自定义代码发生。


我在尝试删除我的测试响应中的csrf html属性或将其放入我的html页面的渲染中遇到了一些困难。我该如何实现其中任何一个? - Jordon Birk

0

我曾经遇到过类似的问题,所以写了一个函数来移除所有的 CSRF 令牌。

def test_home_page_returns_correct_html(self):
    request = HttpRequest()

    # Removes all the csrf token strings
    def rem_csrf_token(string):
        # Will contain everything before the token
        startStr = ''
        # Will contain everything after the token
        endStr = ''
        # Will carrry the final output
        finalStr = string

        # The approach is to keep finding the csrf token and remove it from the final string until there is no
        # more token left and the str.index() method raises value arror
        try:
            while True:
                # The beginning of the csrf token
                ind = finalStr.index('<input type="hidden" name="csrfmiddlewaretoken"')
                # The token end index
                ind2 = finalStr.index('">', ind, finalStr.index('</form>'))

                # Slicing the start and end string
                startStr = finalStr[:ind]
                endStr = finalStr[ind2+2:]

                # Saving the final value (after removing one csrf token) and looping again
                finalStr = startStr +endStr
        except ValueError:
            # It will only be returned after all the tokens have been removed :)
            return finalStr

    response = home_page(request)
    expected_html = render_to_string('lists/home.html')
    csrf_free_response = rem_csrf_token(response.content.decode())

    self.assertEqual(csrf_free_response,
                    expected_html, f'{expected_html}\n{csrf_free_response}')

0

我也遇到了这个问题(使用最新的python 3.6.12和django 1.11.29,根据书的第二版)。

我的解决方案不能回答你的问题“如何渲染token”,但它确实回答了“如何通过将渲染后的模板与返回的视图响应进行比较来传递测试”的问题。

我使用了以下代码:

class HomePageTest(TestCase):
    def remove_csrf_tag(self, text):
        '''Remove csrf tag from text'''
        return re.sub(r'<[^>]*csrfmiddlewaretoken[^>]*>', '', text)

    def test_home_page_is_about_todo_lists(self):
        # Make an HTTP request
        request = HttpRequest()

        # Call home page view function
        response = home_page(request)

        # Assess if response contains the HTML we're looking for

        # First read and open the template file ..
        expected_content = render_to_string('lists/home.html', request=request)

        print(len(response.content.decode()))

        # .. then check if response is equal to template file
        # (note that response is in bytecode, hence decode() method)
        self.assertEqual(
            self.remove_csrf_tag(response.content.decode()),
            self.remove_csrf_tag(expected_content),
        )

顺便说一下:我是基于这个答案的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接