【问题标题】:CRSF Token Interfering With TDD - Is there a variable that stores csrf output?CSRF 令牌干扰 TDD - 是否有存储 csrf 输出的变量?
【发布时间】:2016-06-05 13:45:06
【问题描述】:

因此,在将预期与实际 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:\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>'

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

【问题讨论】:

  • 你能展示更多你的测试代码吗?默认情况下,如果您使用的是内置的 Django 测试客户端,这应该不是问题。根据文档,By default, the test client will disable any CSRF checks performed by your site.
  • 我已更新问题以包括错误、设置等。我将检查刚刚发布的答案,看看它的表现如何。谢谢。

标签: python django tdd csrf django-csrf


【解决方案1】:

如果您使用 Django TestCase 类,CSRF 令牌是您可用的模板上下文数据的一部分:

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 页面呈现时遇到了一些麻烦。我将如何完成其​​中任何一项?
【解决方案2】:

如果可以的话,我想提出一个更好的方法来执行这个测试,使用the built-in Django test client。这将为您处理所有 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,这是一个断言provided by the Django test suite

【讨论】:

  • 我很喜欢这种方法。而且,对于这个问题的任何未来读者来说,为了节省一分钟,reverse() 需要这个导入语句:from django.core.urlresolvers import reverse
【解决方案3】:

从您提供的代码 sn-p 来看,您似乎正在研究“使用 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

【讨论】:

    【解决方案4】:

    我也遇到了这个问题(根据本书的第二版,使用最新的 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:我基于this answer.

    【讨论】:

      【解决方案5】:

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

      【讨论】:

        猜你喜欢
        • 2019-02-23
        • 2015-08-10
        • 2014-10-28
        • 2015-09-26
        • 2012-03-28
        • 1970-01-01
        • 2012-05-15
        • 2017-07-15
        • 1970-01-01
        相关资源
        最近更新 更多