Test Cotton Components
A comprehensive guide for AI agents on effectively using django-cotton-bs5 pytest fixtures to test Django Cotton components. This skill teaches fixture selection, testing patterns, and best practices.
When to Use This Skill
- •Writing new tests for Django Cotton components
- •Testing Bootstrap 5 components wrapped in Cotton syntax
- •Reviewing or refactoring existing Cotton component tests
- •User asks: "How do I test Cotton components?", "Which fixture should I use?", "Test this component"
- •Debugging test failures in Cotton-based templates
Prerequisites
- •Project has
django-cotton-bs5installed (provides pytest fixtures automatically) - •Django and pytest configured in the project
- •Basic understanding of Django templates and Cotton component syntax
Available Fixtures
Fixture Decision Matrix
| Scenario | Input | Fixture | Output |
|---|---|---|---|
| Test single component by name | 'cotton_bs5.alert' | cotton_render | Raw HTML string |
| Test single component with DOM queries | 'cotton_bs5.button' | cotton_render_soup | BeautifulSoup object |
| Test inline multi-component markup | "<c-ul><c-li /></c-ul>" | cotton_render_string | Raw HTML string |
| Test complex nested structure with DOM queries | "<c-card><c-button /></c-card>" | cotton_render_string_soup | BeautifulSoup object |
Quick Rules
- •Use
_soupvariants when you need DOM traversal (find,find_all,select, etc.) - •Use
_stringvariants when testing multiple components together or inline markup - •Use plain variants for simple string assertions ("does it contain X?")
- •All fixtures inject request object automatically — don't create one manually
Testing Patterns
Pattern 1: Single Component - Basic Assertions
Use Case: Verify a component renders with correct CSS classes and content.
Fixture: cotton_render (raw HTML)
def test_alert_renders_with_variant(cotton_render):
"""Test alert component renders correct variant class."""
html = cotton_render(
'cotton_bs5.alert',
message="Warning message",
variant="warning"
)
# Simple string assertions
assert 'alert-warning' in html
assert 'Warning message' in html
assert 'alert' in html
When to use:
- •Testing CSS class presence
- •Verifying text content appears
- •Quick smoke tests
- •Testing component attributes are rendered
Pattern 2: Single Component - DOM Structure
Use Case: Verify component structure, hierarchy, or specific element attributes.
Fixture: cotton_render_soup (parsed HTML)
def test_button_structure_and_attributes(cotton_render_soup):
"""Test button component renders correct HTML structure."""
soup = cotton_render_soup(
'cotton_bs5.button',
text="Click Me",
variant="primary",
size="lg"
)
# DOM traversal and assertions
button = soup.find('button')
assert button is not None
assert 'btn' in button['class']
assert 'btn-primary' in button['class']
assert 'btn-lg' in button['class']
assert button.get_text().strip() == 'Click Me'
When to use:
- •Verifying element attributes (id, class, data-, aria-)
- •Testing DOM hierarchy (parent/child relationships)
- •Checking element counts (
len(soup.find_all('li'))) - •Extracting and validating specific text content
Pattern 3: Multi-Component - Inline Markup
Use Case: Test how multiple components work together in a template string.
Fixture: cotton_render_string (compiles Cotton syntax → raw HTML)
def test_list_with_multiple_items(cotton_render_string):
"""Test list group with multiple items renders correctly."""
html = cotton_render_string("""
<c-list_group>
<c-list_group.item text="First Item" />
<c-list_group.item text="Second Item" active="True" />
<c-list_group.item text="Third Item" />
</c-list_group>
""")
# Verify all items present
assert 'First Item' in html
assert 'Second Item' in html
assert 'Third Item' in html
assert 'active' in html # Active state rendered
When to use:
- •Testing component composition
- •Verifying parent-child component relationships
- •Testing slot content with nested components
- •Quick inline component experiments
Pattern 4: Multi-Component - Complex Structure Testing
Use Case: Test nested components with DOM queries and assertions on structure.
Fixture: cotton_render_string_soup (compiles + parses)
def test_card_with_nested_components(cotton_render_string_soup):
"""Test card component with nested title, body, and buttons."""
soup = cotton_render_string_soup("""
<c-card>
<c-card.title>User Profile</c-card.title>
<c-card.body>
<p>Manage your account settings</p>
<c-button variant='primary'>Edit Profile</c-button>
<c-button variant='secondary'>Cancel</c-button>
</c-card.body>
</c-card>
""")
# Test structure
card = soup.find('div', class_='card')
assert card is not None
# Test title
title = card.find('h5')
assert title.get_text().strip() == 'User Profile'
# Test buttons
buttons = card.find_all('button')
assert len(buttons) == 2
assert 'btn-primary' in buttons[0]['class']
assert buttons[0].get_text().strip() == 'Edit Profile'
assert 'btn-secondary' in buttons[1]['class']
When to use:
- •Testing complex component hierarchies
- •Verifying nested component relationships
- •Counting child elements
- •Testing layouts (cards, modals, accordions with nested content)
Pattern 5: Context Variables & Cotton Attributes
Use Case: Test that global context and component-level attributes work together without leakage.
Fixture: cotton_render_string or cotton_render_string_soup (with context parameter)
def test_component_with_global_context(cotton_render_string_soup):
"""Test component uses both global context and Cotton attributes."""
template = """
<c-alert variant='{{ alert_type }}'>
{{ message }}
</c-alert>
<c-button variant='primary'>{{ button_text }}</c-button>
"""
soup = cotton_render_string_soup(template, context={
'alert_type': 'success',
'message': 'Operation completed',
'button_text': 'Continue'
})
# Verify context variables applied
alert = soup.find('div', class_='alert')
assert 'alert-success' in alert['class']
assert 'Operation completed' in alert.get_text()
button = soup.find('button')
assert 'Continue' in button.get_text()
When to use:
- •Testing components with dynamic attributes
- •Verifying template variable interpolation
- •Testing context isolation (no leakage between components)
- •Integration testing with Django views
Best Practices
✅ DO
- •
Use the simplest fixture that meets your needs
- •
cotton_renderfor simple assertions → faster - •
cotton_render_soupwhen you need DOM queries
- •
- •
Test one concern per test function
python# Good: focused test def test_button_variant_primary(cotton_render): html = cotton_render('cotton_bs5.button', variant='primary') assert 'btn-primary' in html # Good: separate test for size def test_button_size_large(cotton_render): html = cotton_render('cotton_bs5.button', size='lg') assert 'btn-lg' in html - •
Use descriptive test names and docstrings
pythondef test_alert_dismissible_renders_close_button(cotton_render_soup): """Test that dismissible alerts include a close button with correct aria-label.""" soup = cotton_render_soup('cotton_bs5.alert', dismissible=True) # ... - •
Test accessibility attributes
pythondef test_modal_has_aria_labels(cotton_render_soup): """Verify modal has proper ARIA attributes for screen readers.""" soup = cotton_render_soup('cotton_bs5.modal', id='myModal') modal = soup.find('div', class_='modal') assert modal.get('aria-labelledby') is not None assert modal.get('role') == 'dialog' - •
Test negative cases (attributes NOT present)
pythondef test_button_without_icon_has_no_icon_element(cotton_render_soup): """Verify button without icon doesn't render icon element.""" soup = cotton_render_soup('cotton_bs5.button', text='Click') icon = soup.find('i') # or svg, depending on icon implementation assert icon is None
❌ DON'T
- •
Don't create request objects manually — fixtures handle it
python# Bad def test_component(cotton_render): request = RequestFactory().get("/") # Unnecessary! # ... # Good def test_component(cotton_render): html = cotton_render('component_name') # ... - •
Don't use
cotton_render_soupfor simple string checkspython# Inefficient - parsing overhead def test_alert_has_text(cotton_render_soup): soup = cotton_render_soup('cotton_bs5.alert', message='Hello') assert 'Hello' in str(soup) # Better - direct string assertion def test_alert_has_text(cotton_render): html = cotton_render('cotton_bs5.alert', message='Hello') assert 'Hello' in html - •
Don't test Bootstrap CSS implementation details — test your component's behavior
python# Bad - too brittle, tests Bootstrap internals def test_button_has_exact_padding(): assert 'padding: 0.375rem 0.75rem' in html # Good - test component renders correct classes def test_button_applies_size_class(cotton_render): html = cotton_render('cotton_bs5.button', size='sm') assert 'btn-sm' in html - •
Don't duplicate tests across fixtures unless testing different aspects
python# Bad - redundant def test_alert_with_render(cotton_render): html = cotton_render('cotton_bs5.alert') assert 'alert' in html def test_alert_with_soup(cotton_render_soup): # Same test! soup = cotton_render_soup('cotton_bs5.alert') assert 'alert' in str(soup) # Good - different concerns def test_alert_css_class(cotton_render): html = cotton_render('cotton_bs5.alert') assert 'alert' in html def test_alert_structure(cotton_render_soup): soup = cotton_render_soup('cotton_bs5.alert') alert_div = soup.find('div', class_='alert') assert alert_div.get('role') == 'alert'
Common Testing Scenarios
Testing Slots (Named and Default)
def test_button_slot_content(cotton_render_string_soup):
"""Test button with slot content instead of text attribute."""
soup = cotton_render_string_soup("""
<c-button variant='primary'>
<i class='icon-save'></i> Save Changes
</c-button>
""")
button = soup.find('button')
assert button.find('i', class_='icon-save') is not None
assert 'Save Changes' in button.get_text()
Testing Conditional Rendering
def test_alert_dismissible_button(cotton_render_soup):
"""Test close button only appears when dismissible=True."""
# With dismissible
soup = cotton_render_soup('cotton_bs5.alert', dismissible=True)
assert soup.find('button', class_='btn-close') is not None
# Without dismissible
soup = cotton_render_soup('cotton_bs5.alert', dismissible=False)
assert soup.find('button', class_='btn-close') is None
Testing Dynamic Classes
def test_button_combines_variant_and_size(cotton_render_soup):
"""Test button correctly combines variant and size classes."""
soup = cotton_render_soup(
'cotton_bs5.button',
variant='success',
size='lg'
)
button = soup.find('button')
classes = button['class']
assert 'btn' in classes
assert 'btn-success' in classes
assert 'btn-lg' in classes
Testing Data Attributes
def test_modal_data_attributes(cotton_render_soup):
"""Test modal renders correct data-bs-* attributes."""
soup = cotton_render_soup('cotton_bs5.modal', id='testModal')
toggle_button = soup.find('button', attrs={'data-bs-toggle': 'modal'})
assert toggle_button is not None
assert toggle_button.get('data-bs-target') == '#testModal'
Troubleshooting
Issue: "Fixture not found"
Problem: Test can't find cotton fixtures.
Solution: Ensure django-cotton-bs5 is installed and pytest can discover it:
pip install django-cotton-bs5 pytest --fixtures | grep cotton_render
All four fixtures should appear. If not, check INSTALLED_APPS includes cotton_bs5.
Issue: "Template syntax error"
Problem: Cotton syntax not compiling in cotton_render_string.
Solution:
- •Check your Cotton component syntax is valid
- •Verify component is registered in Django
- •Test component works in actual template first
- •Use
cotton_render('component.name')to isolate if issue is component vs string rendering
Issue: "Can't find element with BeautifulSoup"
Problem: soup.find() returns None.
Solution:
- •Print the HTML to inspect structure:
print(soup.prettify()) - •Check if component rendered at all
- •Verify CSS selector or attributes are correct
- •Component might use different tag than expected
# Debug: print HTML structure
def test_debug_component(cotton_render_soup):
soup = cotton_render_soup('cotton_bs5.card')
print(soup.prettify()) # Inspect actual output
# Now adjust assertions based on what you see
Issue: "Context variable not rendering"
Problem: Template variables like {{ variable }} show as literal text.
Solution: Pass context dict to fixture:
# Wrong - variable won't render
html = cotton_render_string("<c-alert>{{ msg }}</c-alert>")
# Right - pass context
html = cotton_render_string(
"<c-alert>{{ msg }}</c-alert>",
context={'msg': 'Hello'}
)
Example: Complete Test Suite for a Component
"""Test suite for Button component."""
import pytest
class TestButtonComponent:
"""Test cotton_bs5.button component."""
def test_button_basic_rendering(self, cotton_render):
"""Test button renders with default classes."""
html = cotton_render('cotton_bs5.button', text='Click')
assert 'btn' in html
assert 'Click' in html
def test_button_variants(self, cotton_render):
"""Test button renders all Bootstrap variants."""
variants = ['primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark']
for variant in variants:
html = cotton_render('cotton_bs5.button', variant=variant)
assert f'btn-{variant}' in html
def test_button_sizes(self, cotton_render_soup):
"""Test button renders with size classes."""
# Small
soup = cotton_render_soup('cotton_bs5.button', size='sm')
assert 'btn-sm' in soup.find('button')['class']
# Large
soup = cotton_render_soup('cotton_bs5.button', size='lg')
assert 'btn-lg' in soup.find('button')['class']
def test_button_as_link(self, cotton_render_soup):
"""Test button renders as anchor tag when href provided."""
soup = cotton_render_soup('cotton_bs5.button', href='/test/', text='Link')
link = soup.find('a')
assert link is not None
assert link.get('href') == '/test/'
assert 'btn' in link['class']
def test_button_disabled(self, cotton_render_soup):
"""Test button disabled attribute."""
soup = cotton_render_soup('cotton_bs5.button', disabled=True)
button = soup.find('button')
assert button.get('disabled') is not None
def test_button_with_icon_slot(self, cotton_render_string_soup):
"""Test button with icon in slot content."""
soup = cotton_render_string_soup("""
<c-button variant='primary'>
<i class='bi bi-save'></i> Save
</c-button>
""")
button = soup.find('button')
icon = button.find('i', class_='bi-save')
assert icon is not None
assert 'Save' in button.get_text()
Summary
Use this skill to guide fixture selection and write comprehensive, maintainable tests for Django Cotton components. Remember:
- •Start simple: Use
cotton_renderfor basic tests - •Use BeautifulSoup (
_soup) when you need DOM queries - •Test multi-component scenarios with
cotton_render_stringvariants - •Test accessibility (ARIA attributes, roles, labels)
- •Follow one-concern-per-test principle
- •Document expected behavior in docstrings
The fixtures make testing Cotton components straightforward — choose the right tool for each testing need!