Write unittests¶
In the quick start guide we created our first Brython app. Continuing it, let’s create some tests, check if H1 is inserted into DOM, and our code is working properly.
To run client side tests, you need to activate django-brython testing middleware:
# project/settings.py
MIDDLEWARE = [
# ...
'django_brython.middleware.BrythonUnittestMiddleware'
]
Create test class, which will run in the browser. Place the testing logic into test_* functions as you do in normal Django testcases.
# frontend/tests/brython.py
from browser import document
class BrythonTest:
def test_example(self):
h1 = document.getElementById('main')
# Force AssertionError
assert h1.text == 'wrong content'
Start the dev server, and try to open the index page, with the following query string:
http://localhost:8000/backend/index/?__BRYTHON_UNITTEST__=frontend.tests.brython.BrythonTest.test_example
So any view can test against any testcase specified by the full path in HTTP GET parameter. If you check the debug console in your browser, the assertion is shown.
The MIDDLEWARE injekts the testing code at the end of your HTML, and runs it.
Integrate test into Django testing framework¶
If you would like to run the previously created test when you call,:
python manage.py test
then you need to connect the client side test with Django testing framework.
Firstly create StaticLiveServerTestCase, which runs a browser with Selenium webdriver:
# frontend/tests/test_django.py
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium import webdriver
from .brython import BrythonTest
class Test1(StaticLiveServerTestCase, BrythonTest):
def setUp(self):
self.driver = webdriver.Chrome()
def tearDown(self):
self.driver.quit()
This class inherits from LiveServerTestCase and also the previously created BrythonTest class.
Modify the BrythonTest, and decorate the test_* methods:
from browser import document
from django_brython.testing import location, reverse
class BrythonTest:
@location(reverse('backend:index'))
def test_example(self):
h1 = document.getElementById('main')
assert h1.text == 'wrong content'
This decorator tells to the test runner which URL to open before running the client side Brython tests. Please use django_brython reverse method instead of Django’s builtin.
If you run the tests, the Brython exceptions will shown in the console:
python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
E
======================================================================
ERROR: test_example (frontend.tests.test_django.Test1)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/project/django_brython/testing.py", line 64, in dec
return runner()
File "/tmp/project/django_brython/testing.py", line 49, in __call__
raise Exception(test_result['exception'])
Exception: Traceback (most recent call last):
File "http://localhost:48535/backend/index/?__BRYTHON_UNITTEST__=frontend.tests.brython.BrythonTest.test_example/__main__2", line 9, in <module>
func()
File "/brython/django_brython/testing.py", line 11, in dec
return function(inst)
File "/brython/frontend/tests/brython.py", line 11, in test_example
assert h1.textContent == 'wrong content'
AssertionError: AssertionError
----------------------------------------------------------------------
Ran 1 test in 13.104s
FAILED (errors=1)
Destroying test database for alias 'default'...
Race conditions¶
If you run this test a lot of time, then sometimes you’ll get AttributeError:
brython.js:6308 Traceback (most recent call last):
File "http://localhost:8000/backend/index/?__BRYTHON_UNITTEST__=frontend.tests.brython.BrythonTest.test_example/__main__2", line 7, in <module>
func()
File "/brython/django_brython/testing.py", line 36, in dec
return function(inst)
File "/brython/frontend/tests/brython.py", line 12, in test_example
assert h.text == "wrong content"
AttributeError: text
This effect is because there is some race condittion between the client site codes (frontend/main.py) and the unittests. Each Brython script is loaded with ajax calls, in your client codes can have async functions too. That’s why the scripts running order isn’t predicted.
Solution 1:
Call your business logic from unittest. Modify the main.py, create a function:
# frontend/main.py
from browser import document, html
def display():
print('Hello World from Brython')
# Insert Header into document body
document <= html.H1("HELLO FROM BRYTHON", Id="main")
if __name__ == '__main__':
display()
from frontend.main import display
class BrythonTest:
@location(reverse('backend:index'))
def test_example(self):
display()
h = document.getElementById("main")
assert h.text == "HELLO FROM BRYTHON"
Solution 2:
Setup a wait_for condition in your location decorator. The test runner will wait until the specified condition is met:
class BrythonTest:
@location(reverse('backend:index'), wait_for=lambda: document.getElementById("main") is not None)
def test_example(self):
h = document.getElementById("main")
assert h.text == "HELLO FROM BRYTHON"