CRSF 令牌干扰 TDD - 是否有存储 csrf 输出的变量



所以,当使用表单输入将预期与实际 html 进行比较时,我一直在 Django 中返回失败测试,所以我打印出结果并意识到差异是相当简单的行,由我的{% 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:UsersMePycharmProjectssuperlistsliststests.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>nn    <table
 id="id_list_t[82 chars]tml>'
----------------------------------------------------------------------

从你提供的代码片段来看,你似乎正在研究"TestDriven Development with Python"一书中的示例,但没有使用Django 1.8。

这本书的Google网上论坛讨论中的这篇文章解决了测试失败的问题,因为你正在经历它:

https://groups.google.com/forum/#!topic/obey-the-testing-goat-book/fwY7ifEWKMU/discussion

这个 GitHub 问题(来自本书的官方存储库)描述了与您的问题一致的修复程序:

https://github.com/hjwp/book-example/issues/8

如果可以的话,我想提出一种更好的方法来执行这个测试,使用内置的 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 测试套件提供的断言。

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 %},因此无法从响应上下文中向render_to_string方法提供 CSRF 令牌以使其使用相同的值。相反,您必须在 render_to_string 的结果中替换它,也许首先使用硒查找输入元素(使其成为测试本身)。然而,这个测试有多大用处是值得怀疑的。它只会有助于确保存在CSRF令牌,但无论如何已经在服务器上以正常工作模式进行了检查。

基本上,你应该测试任何你直接影响代码的东西,而不是Django magic提供的任何内容。 例如,如果你正在做自定义表单验证,你应该测试它,而不是 Django 给你的任何验证。如果要在 ListView get_object或 DetailView 中更改查询集(自定义筛选等),则应检查生成的列表和 404 错误是否根据自定义代码发生。

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

我的解决方案没有回答您的问题"如何呈现令牌",但它确实回答了"如何通过将渲染的模板与返回的视图响应进行比较的测试"。

我使用了以下代码:

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),
        )

PS:我基于这个答案。

我有类似的问题,所以做了一个函数来删除所有的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}')

最新更新