Verdict
Yes
Yes, test server-rendered views minimally: render each page once in a request test with realistic records and assert the text users need, and write no separate view specs for templates that only print fields.
Why
- Blast radiususers
- Change frequencyregularly
- Detectabilitysame-day
- Reversibilitytrivial
- Test costmoderate
Test views minimally: render each page once in a request test. For a Rails, Django or ASP.NET Core MVC template that prints a record and has a few conditionals, Blast radius is users and Change frequency is regularly, because templates change with most features. Detectability is same-day, because a template error returns a 500 that the error tracker reports within hours, and Reversibility is trivial, because a view stores nothing. Test cost is moderate: a request test needs records in a test database and changes with the page's text.
| When | Decision | Why |
|---|---|---|
| The template prints fields with no conditionals, and its author opens the page after each change | Do not test the view; look at the page in a browser before merging | Detectability moves to immediately, because the author sees a broken page at once, and Reversibility stays trivial |
| A request test for the page runs in CI with records that take each branch of the template, such as a project with an owner and one without | Do not add separate view specs for the page | Detectability moves to immediately, because a template error in any branch fails CI before merge, and Reversibility stays trivial |
| The template shows some fields only to some roles, such as customer email addresses that only admins may see | Test mandatory: render the page as each role and assert the hidden fields are absent | Blast radius rises to safety-or-legal and Detectability to never, because personal data shown to the wrong role raises no error |
| The view renders a form whose input names bind to model fields, such as a profile edit form | Test: submit the form in a request test and assert every field reaches the saved record | Detectability moves to eventually and Reversibility rises to with-effort, because a renamed input saves an empty field without an error |
| The view formats prices or an order total that customers pay | Test mandatory: assert the exact amount and currency shown for each pricing case | Blast radius rises to money and Reversibility to costly, because customers buy at the amount on the page |
| The view is an Android screen in an app that users update only through the store | Test: assert what the screen shows in its loaded, empty and error states with Espresso or Robolectric | Reversibility rises to costly, because every fix waits for a store release |
What breaks if you don't test
A template breaks on the branch its author never opened: @project.owner.name raises NoMethodError for the first project without an owner, and its users get an error page until a fix ships. RSpec controller tests stay green, because controller specs stub views by default.
What you lose if you over-test
A view spec for each template assigns the variables itself and asserts on CSS selectors, so moving a heading into a card breaks it while the page still works. A view spec also passes when the controller stops setting a variable the template reads, because the spec assigns that variable, so the page fails in production with a green suite.
How to test
Test views at the request level, one test per page: integration tests in Rails, the Django test client with assertContains, or WebApplicationFactory in ASP.NET Core.
- Create the records the page shows, including one for the rarer branch of each conditional, such as a missing association.
- Request the page and assert status 200.
- Assert the text users need, such as the record's name, without CSS selectors.
Add a regression test when a template bug reaches production.
When the answer changes
- The template decides which role sees which fields.
- The page shows prices or totals.
- The view builds a form whose fields the app saves.
Real incident + Code example
The project page with no owner
On a Rails project tracker I worked on, a nightly job that archived departed staff set owner_id to null on their projects. The project page printed @project.owner.name, and its controller spec passed because RSpec stubbed the view. By morning, customers had hit 500 errors on 37 projects; an hour after the alert, the page printed "No owner" instead. We added this request spec:
RSpec.describe "Project page", type: :request do
it "renders a project whose owner has left" do
project = Project.create!(name: "Q3 roadmap", owner: nil)
get project_path(project)
expect(response).to have_http_status(:ok)
expect(response.body).to include("Q3 roadmap", "No owner")
end
end
The spec runs the real controller and template, so a nil owner fails CI.
Related questions
FAQ
- When should ASP.NET MVC views be unit tested?
Unit test an ASP.NET MVC view only when it holds logic, such as fields hidden by role; cover other views with one integration test per page through
WebApplicationFactory. ASP.NET Core compiles Razor views at build time, so a syntax error fails the build, but a null model property throws only when the page renders.- Should I test view attributes in unit tests?
Test only the view attributes that your code changes at runtime, such as an empty-state message that appears when a list is empty. An assertion on an attribute declared in the layout file, such as a text colour, repeats the declaration.
- When should I test views separately in a Cucumber and RSpec workflow?
Test a view separately only for template branches that no Cucumber scenario reaches, such as a page that differs by role. For each such branch, a request spec with the matching records costs less than a new scenario.
- Should I test a generic DetailView in Django?
Test your Django DetailView with one request, not the generic class: request its URL for a real record and assert the page contains the record's fields. Django's own tests cover the generic view code, so that request checks your URL pattern, queryset and template.