Django 中的 csrf_token 與單元測試 在 "《Python Web開發:測試驅動方法》" 一書中作者使用的 Django 版本是 1.7,而我使用的是1.9.7版(官網已經更新到1.10了)。這就導致書中給出的代碼可能有“過時”的部分。 比如,下麵是第三章一個單元測試 的代碼,運 ...
Django 中的 csrf_token 與單元測試
在《Python Web開發:測試驅動方法》一書中作者使用的 Django 版本是 1.7,而我使用的是1.9.7版(官網已經更新到1.10了)。這就導致書中給出的代碼可能有“過時”的部分。
比如,下麵是第三章一個單元測試tests.py
的代碼,運行沒有問題。但是當第五章引入表單後,相應模板中需在<form>
標簽內加入CSRF令牌{% csrf_token %}
。此時再次運行此單元測試會報錯。
from django.test import TestCase
from django.core.urlresolvers import resolve
from django.http import HttpRequest
from django.template.loader import render_to_string
from .views import home_page
class HomePageTest(TestCase):
def test_root_url_resolves_to_home_page_view(self):
found = resolve('/')
self.assertEqual(found.func, home_page)
def test_home_page_returns_correct_html(self):
request = HttpRequest()
response = home_page(request)
expected_html = render_to_string('home.html')
self.assertEqual(response.content.decode(), expected_html)
錯誤信息:
$ python3 manage.py test lists
Creating test database for alias 'default'...
F.
======================================================================
FAIL: test_home_page_returns_correct_html (lists.tests.HomePageTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/panzeyan/PycharmProjects/TDD/superlists/lists/tests.py", line 20, in test_home_page_returns_correct_html
self.assertEqual(response.content.decode(), expected_html)
AssertionError: '<htm[240 chars] <input type=\'hidden\' name=\'csrfmiddlew[184 chars]l>\n' != '<htm[240 chars] \n </form>\n\n <table id="i[87 chars]l>\n'
----------------------------------------------------------------------
Ran 2 tests in 0.256s
FAILED (failures=1)
Destroying test database for alias 'default'...
根據錯誤信息,是最後一行的斷言self.assertEqual(response.content.decode(), expected_html)
導致測試失敗。
由於AssertionError
信息顯示不完整,所以將該行斷言註釋掉,添加2行代碼,列印出response.content.decode()
和 expected_html
的全部內容。
print('response.content.decode()\n', response.content.decode())
print('expected_html\n', expected_html)
運行測試:
$ python3 manage.py test lists
Creating test database for alias 'default'...
response.content.decode()
<html>
<head>
<title>To-Do lists</title>
</head>
<body>
<h1>Your To-Do list</h1>
<form method="post">
<input name="item_text" id="id_new_item" placeholder="Enter a to-do item" />
<input type='hidden' name='csrfmiddlewaretoken' value='tl2rZy1RBSLY75DD2ysZ4KHF0DePGWQs' />
</form>
<table id="id_list_table">
<tr><td>1: </td></tr>
</table>
</body>
</html>
expected_html
<html>
<head>
<title>To-Do lists</title>
</head>
<body>
<h1>Your To-Do list</h1>
<form method="post">
<input name="item_text" id="id_new_item" placeholder="Enter a to-do item" />
</form>
<table id="id_list_table">
<tr><td>1: </td></tr>
</table>
</body>
</html>
..
----------------------------------------------------------------------
Ran 2 tests in 0.015s
OK
Destroying test database for alias 'default'...
在渲染模板時,Django 會把這個模板標簽替換成一個<input type="hidden">
元素,其值是CSRF 令牌。
從上面的html代碼可以看出,通過視圖函數home_page()
渲染得到的響應包含csrf轉換的<input>
元素,而render_to_string()
則未生成該部分,所以導致測試失敗。
以 “django csrf_token 測試”為關鍵字google下,發現已經有人碰到這個問題。可惜代碼不全,網頁中提到的參考鏈接已經失效。
再以失效鏈接中的“django-csrf_token-when-using-render_to_string”為關鍵詞google,stackoverflow上有人給出一個簡單的解決方法,在tests.py
中的render_to_string()
函數內中加一個參數
expected_html = render_to_string('home.html', request=request)
運行測試,不再報錯,問題解決。
$ python3 manage.py test lists
Creating test database for alias 'default'...
..
----------------------------------------------------------------------
Ran 2 tests in 0.012s
OK
Destroying test database for alias 'default'...