A navigation menu depends on the content of a site. In this tutorial we create the content of a Wagtail site with Factory Boy and with it test the navigation menu.
In an earlier tutorial we have created a navigation menu which can handle Wagtail pages as well as pages outside of the Wagtail tree, is multilingual and editable via admin. In another tutorial we have set up a factory for testing Wagtail pages. We are going to bring this together to test the navigation menu. Since we have already set up a page factory we can create some content for the site. Let's set up some content for our tests: a site, a homepage
, an articleindexpage
and two articlepage
s. Note that we set the show_in_menus
fields to True
. In our /tests
directory create a file test_menus.py
:
from .factories import HomePageFactory, ArticlePageFactory, ArticleIndexPageFactory
from django.test import TestCase
from wagtail.core.models import Page, Site
class TestMenus(TestCase):
@classmethod
def setUpTestData(cls):
cls.site = Site.objects.create(is_default_site=True, root_page=Page.get_first_root_node())
cls.homepage = HomePageFactory()
cls.articleindexpage = ArticleIndexPageFactory(parent=cls.homepage)
cls.articlepage1 = ArticlePageFactory(parent=cls.articleindexpage, show_in_menus=True)
cls.articlepage2 = ArticlePageFactory(parent=cls.articleindexpage, show_in_menus=True)
Before we can create a menu and menu items we need to create a factory for them, in our file /tests/factories.py
. The factory for the menu is simple:
from ..models import Menu
class MenuFactory(factory.django.DjangoModelFactory):
class Meta:
model = Menu
title = factory.Sequence(lambda n: 'Menu {0}'.format(n))
We already explained the use of Sequence
to automatically generate a title. The menu item factory can be defined as follows:
from ..models import MenuItem
class MenuItemFactory(factory.django.DjangoModelFactory):
class Meta:
model = MenuItem
menu = factory.SubFactory(MenuFactory)
The foreign key relationship between the menu and the menu item is established through a SubFactory
. In the test setup in the file test_menus.py
we create a menu and three menu items with a few fields set to different values:
cls.menu = MenuFactory()
# for the menu-items we need to establish a sort order (class Orderable)
cls.menuitem_ordinary = MenuItemFactory(menu=cls.menu, sort_order=1, title="Ordinary",
link_url='/ordinary/', show_when='always')
cls.menuitem_guest = MenuItemFactory(menu=cls.menu, sort_order=2, title="Guest", link_url='/guest/',
show_when='not_logged_in')
cls.menuitem_articlepage1 = MenuItemFactory(menu=cls.menu, sort_order=3, link_page=cls.articlepage1)
Since the model MenuItem
is subclassed from the class Orderable
, we manually add a sort_order
. Furthermore we set a title
, a link_url
and different values for the field show_when
, to be used in our tests. Time to write our first test: check whether our function get_menu
correctly generates a menu from a page which has children:
def test_get_menu_of_page(self):
menu = get_menu(slug=None, page=self.articleindexpage, logged_in=True)
self.assertEqual(menu[0]['title'], self.articlepage1.title)
self.assertEqual(menu[0]['url'], self.articlepage1.url)
self.assertEqual(menu[0]['page'].title, self.articlepage1.title)
self.assertEqual(len(menu), 2)
We can run the test with:
python3 manage.py test cms.tests.test_menus
So far so good. Now let's test a handmade menu:
from django.utils import translation
def test_get_handmade_menu(self):
menu = get_menu(self.menu.slug, None, True)
self.assertEqual(menu[0]['title'], 'Ordinary')
# the expected url is in the current language
expected_url = '/' + translation.get_language() + '/ordinary/'
self.assertEqual(menu[0]['url'], expected_url)
The get_menu
function generates the menu from the slug
of the menu instance, which is automatically generated from the title
in our model. The first menu item should then have the title
of the first menu item. Since our site is multilingual, we expect the url to be prefixed with the current language code, so we use the function get_language()
to check this. Testing whether the function get_menu
picks up the logged_in
parameter is straightforward:
def test_get_menu_logged_in_or_not(self):
menu = get_menu(self.menu.slug, None, True)
# menu should only have two items
self.assertEqual(len(menu), 2)
menu = get_menu(self.menu.slug, None, False)
self.assertEqual(len(menu), 3)
We want to add some more tests on how get_menu
handles languages. For that we introduce a foreign language code and a page in a foreign language in our setup:
from django.conf import settings
from wagtailtrans.models import Language, TranslatablePage
cls.foreign_language_code = [code for code, lang in settings.LANGUAGES if code != settings.LANGUAGE_CODE][0]
# note: Wagtailtrans automatically creates a language tree for every language that is defined
cls.foreign_language = Language.objects.get_or_create(code=cls.foreign_language_code)[0]
cls.foreign_articlepage1 = TranslatablePage.objects.get(language=cls.foreign_language, canonical_page=cls.articlepage1)
We retrieve the foreign language code from our settings. Wagtailtrans has a special Language
model, of which we need an instance. When a page is created (in our case articlepage1
), Wagtailtrans automatically generates a page for every language in settings. Therefore we are able to retrieve a foreign language version of that page by using the fields language
and canonical_page
. Now we are able to test the trans_page()
and trans_url()
method of the MenuItem
model:
def test_menuitem_trans_page_for_foreign_language(self):
self.assertEqual(self.menuitem_articlepage1.trans_page(self.foreign_language_code).url, self.foreign_articlepage1.url)
def test_menuitem_trans_page_for_canonical_language(self):
self.assertEqual(self.menuitem_articlepage1.trans_page(settings.LANGUAGE_CODE).url, self.articlepage1.url)
def test_menuitem_trans_url_method(self):
self.assertEqual(self.menuitem_articlepage1.trans_url(self.foreign_language_code), self.foreign_articlepage1.url)
Run coverage
to check how much of the code we have covered thus far:
coverage run --source=cms manage.py test cms
coverage html
Our template tags are covered well enough. Time to move on to testing the language view in views.py
.
Comment on this article (sign in first or confirm by name and email below)