Skip to content

Comprehensive test coverage for the nine Grails 8 v8 sample-app guides - #510

Merged
jamesfredley merged 9 commits into
masterfrom
add-comprehensive-tests-v8-guides
May 28, 2026
Merged

Comprehensive test coverage for the nine Grails 8 v8 sample-app guides#510
jamesfredley merged 9 commits into
masterfrom
add-comprehensive-tests-v8-guides

Conversation

@jamesfredley

Copy link
Copy Markdown
Contributor

Summary

Comprehensive, layered test coverage for the nine Grails 8 (v8) sample-app guides, following the worked example set by grails-transactional-events. Every sample app was cloned, built, and run against the current Grails 8.0.0-SNAPSHOT stack (Spring Boot 4.0.5, Groovy 4.0.32, JDK 21). All nine were broken or untested in some way - the guide rendering pipeline only checks YAML + AsciiDoc + include:: resolution, never compiling or running the sample app, so a guide can render perfectly and be non-functional.

Each guide's upstream sample app (grails-guides/<name> @ grails8) now has a green test + integrationTest suite, the fixes are mirrored into the vendored snippets/, and prose was corrected wherever it described code that never ran. A Testing chapter was added where the guide lacked one.

Per-guide results

Guide Tests green Key fixes
grails-spock-test-tour 16 unit + 4 integ/Geb testcontainers-bom; getFieldError; flush; show() via params.id; HTML view for Geb; committed seed; JaCoCo
grails-rest-library 19 unit + 8 integ/func app did not compile (BootStrap Logger, gson Date.format); bulkCreate 500→422 via MessageSource; testcontainers-bom; web test support
grails-github-actions-cicd 3 unit + 2 integ + 1 func CI ran a functionalTest task that did not exist; wired it + a real Message domain so all 4 stages run real tests
grails-htmx 5 unit + 3 integ + 6 Geb every HTMX endpoint 404'd (ambiguous createLink reverse-mapping) → createLink(uri:); Geb tests for every swap
grails-tailwindcss 5 Geb replaced placeholder spec; Geb proves the built CSS serves, .card/.btn-primary apply, dark-mode toggle persists
grails-vite-spa 7 unit + 8 integ/func SPA serving chain broken: missing root wrapper; gson double-encoding + Long count 500; forward 404 → classpath render; /assets/** 404 → AssetController
grails-fields-custom-widgets-and-wrappers 12 unit + 8 integ/Geb 3 render bugs: ISBN pattern \d GSP-escape; domainClass.simpleNamejavaClass.simpleName; m2m seed never persisted (transaction)
grails-multi-module 6 unit + 4 func missing root wrapper; wired the documented grails-exploded + grails { plugins } dev-reload path; admin REST routing (resources mapping)
grails-docker-bootbuildimage 4 func testcontainers-bom; db-migration test schema; functional tests of the actuator health/liveness/readiness probes the Docker HEALTHCHECK polls

Verification

  • Every sample app: ./gradlew test and ./gradlew integrationTest green (Docker + Testcontainers PostgreSQL/Selenium where applicable). Sample apps pushed to grails-guides/<name> @ grails8.
  • ./gradlew validateGuides -PvalidationMode=both: 93 guides parsed, 0 errors.
  • Each touched guide renders every chapter with all include:: directives resolved (renderGuide_<name>_8 --no-configuration-cache).

Scope is limited to the nine guides/<name>/v8 trees plus conf/guides.yml (new testing chapters registered in the toc).

Assisted-by: claude-code:claude-opus-4-7

Every Spock layer the guide demonstrates now compiles and passes against
Grails 8.0.0-SNAPSHOT (16 unit + 4 integration/functional/Geb green). The
specs were authored but never executed and several were broken.

Snippets re-vendored from the fixed sample app:

- BookSpec: getFieldError(...), the nullable-vs-blank map-constructor gotcha,
  added blank + maxSize constraint cases
- BookServiceSpec: flush seeded books so the GORM finders see them
- BookControllerSpec: RestfulController.show() via params.id (no-arg action)
- BookIntegrationSpec: flush before the duplicate save; a @service save throws
  ValidationException
- BookController / UrlMappings / book/index.gsp: render an HTML table at /books
- BookFunctionalSpec: seed committed data via withNewTransaction

Prose corrected where it described code that never ran or made false claims:

- domainUnitTest: getFieldError + the nullable-vs-blank gotcha
- integrationTest: Testcontainers PostgreSQL (not H2); @service save throws
  ValidationException (not hasErrors)
- functionalTest: new "page the test drives" section + committed-seed rationale
- platform: jacoco executionData uses layout.buildDirectory, not deprecated buildDir
- whatYouWillBuild: index action also renders an HTML table

Assisted-by: claude-code:claude-opus-4-7
The sample app did not compile against Grails 8.0.0-SNAPSHOT and had no
tests. It now builds and ships 27 green tests (19 unit + 8 integration/
functional). Re-vendored snippets and corrected prose:

- BootStrap snippet: drop the manual Logger field that collides with the
  injected log property (compile failure); bootstrapData chapter now
  describes the Author.withTransaction wrapper, not @transactional
- BookController snippet: bulkCreate uses the injected MessageSource (the
  message() taglib is not available in a rest-api controller), so an
  invalid bulk POST returns the documented 422 instead of a 500

New "Testing the API" chapter (registered in conf/guides.yml toc) with
vendored snippets:

- application-test.yml: testcontainers PostgreSQL + create-drop schema +
  databasemigration.updateOnStart false (the database-migration plugin
  otherwise blocks schema creation in the test env)
- BookSpec / AuthorSpec: domain constraints and the hasMany mapping
- BookControllerSpec: pagination clamping
- BookIntegrationSpec: filtering / paging / relationships (seed-aware)
- BookFunctionalSpec: the JSON surface over HTTP incl. the all-or-nothing
  bulkCreate contract
- documents the testcontainers-bom and grails-testing-support-web deps

Assisted-by: claude-code:claude-opus-4-7
The CI workflow ran ./gradlew functionalTest, but the guide never showed the
task that defines it and the app had no unit or integration tests, so two of
the four stages were hollow and the functional stage would fail. The sample
app now runs 6 green tests across the three stages (3 unit + 2 integration +
1 Geb functional).

Re-vendored snippets and expanded the testing chapters:

- unitTests: adds the Message domain + MessageSpec (DomainUnitTest)
- integrationTests: adds MessageIntegrationSpec and explains the
  integrationTest -> *IntegrationSpec / functionalTest -> *FunctionalSpec split
- functionalTests: documents the build.gradle wiring that actually defines the
  functionalTest task (a second Test task over the integration-test source set,
  filtered to the Geb specs) and shows HomePageFunctionalSpec

Assisted-by: claude-code:claude-opus-4-7
The HTMX interactions never worked: createLink(controller, action) reverse
mapping is ambiguous for the shared /tasks and /tasks/$id REST routes and fell
back to /task/create, which no mapping serves, so every hx-post/patch/delete
404'd. The sample app now drives all five swap types green (5 unit + 3
integration + 6 Geb functional).

- re-vendored the view partials to build HTMX URLs with createLink(uri:
  '/tasks...') so they match the REST mappings
- urlMappings chapter: explains why createLink(uri:) is used instead of
  controller/action reverse mapping for the shared REST routes
- new "Testing the HTMX UI" chapter (registered in conf/guides.yml toc):
  TaskSpec (constraints), TaskIntegrationSpec (findAllByTitleIlike, ordering,
  toggle), TaskTrackerFunctionalSpec (Geb tests of every HTMX swap, with
  withConfirm for delete)

Assisted-by: claude-code:claude-opus-4-7
The sample app's only test was the forge default (a 'Welcome to Grails' title
assertion that never matched the actual 'Welcome to Grails + Tailwind' page),
so it had never run. The app now has a real browser test suite (5 Geb specs).

- new "Testing the Tailwind Build" chapter (registered in conf/guides.yml toc)
  vendoring TailwindFunctionalSpec: asserts the compiled app.css is served,
  that .card/.btn-primary apply real computed styles from the @apply layer,
  and that the class-based dark-mode toggle works and persists to localStorage

Assisted-by: claude-code:claude-opus-4-7
The SPA-serving chain was broken end to end and the backend had no tests.
The sample app now runs 15 green tests (7 unit + 8 integration/functional)
and genuinely serves the SPA same-origin.

Re-vendored snippets and corrected prose:

- index.gson: render the book template with collection: (it was building
  items as JSON strings) and declare Integer bookCount (was Long -> 500)
- SpaController: serve index.html from the classpath (forward url: 404s in a
  rest-api app); spaController chapter rewritten to match, toc title updated
- new AssetController + /assets/** mapping streams the Vite bundle same-origin
  (the rest-api profile serves no static resources); urlMappings chapter
  updated to document both routes

New "Testing the API and SPA Serving" chapter (registered in conf/guides.yml
toc) vendoring BookSpec, BookControllerSpec, BookIntegrationSpec, and
ApiFunctionalSpec (JSON envelope, POST 201/422, same-origin no-CORS, and the
SPA shell + /assets bundle served same-origin - which also exercises the Vite
build + copy integration).

Assisted-by: claude-code:claude-opus-4-7
…ting chapter

Three custom-widget/table rendering bugs threw at runtime and the seed
many-to-many never persisted. The sample app now renders all custom widgets
and runs 20 green tests (12 unit + 8 integration/Geb).

Re-vendored snippets and added prose:

- isbn _widget.gsp: doubled the backslashes in the pattern attribute (\d is an
  illegal Groovy escape in a GSP attribute, so create/edit threw); bookIsbn
  chapter gains a note explaining the GSP escaping
- templates/_fields/_table.gsp: domainClass.javaClass.simpleName (was
  domainClass.simpleName, which threw on HibernatePersistentEntity) - this now
  matches the appWideTable chapter's own prose
- BootStrap: wrap the seed in Book.withTransaction so the Book<->Tag join
  persists; bootstrapData chapter explains why

New "Testing the Widgets and Wrappers" chapter (registered in conf/guides.yml
toc): BookSpec/AuthorSpec (constraints + associations), LibraryIntegrationSpec
(m2m + one-to-one), LibraryFunctionalSpec (Geb tests that the custom f:table,
display widgets, ISBN widget, and association widgets actually render).

Assisted-by: claude-code:claude-opus-4-7
…nippets

The sample app diverged from the guide (the Dev Reload chapter documents the
grails-exploded plugin + grails { plugins { } } block, but the apps used a
plain dependency), the admin REST surface did not route, and the modules had
only the forge-default Geb spec. The sample now runs 10 green tests (6 unit +
4 functional) and matches the documented setup.

Expanded testingPerModule with vendored snippets:

- shared-core BookSpec / BookServiceSpec (the shared domain + @service)
- webapp-admin UrlMappings (the new "/books"(resources:'book') so the
  RestfulController routes) + AdminBookFunctionalSpec (REST CRUD over HTTP)
- webapp-customer CatalogFunctionalSpec (the read-only catalog)

The Dev Reload and Webapps chapters already documented the exploded-plugin +
grails.plugins wiring; the sample app now actually implements it, so the
guide's primary reload recipe works as written.

Assisted-by: claude-code:claude-opus-4-7
The app shipped only the forge-default Geb spec and its test build could not
resolve Testcontainers. The sample now runs 4 green functional tests over the
Actuator health surface the guide configures.

New "Testing the Health Probes" chapter (registered in conf/guides.yml toc)
vendoring:

- application-test.yml: testcontainers-bom dependency note + create-drop schema
  and databasemigration.updateOnStart false (the database-migration plugin
  otherwise blocks schema creation in the test env)
- ActuatorHealthFunctionalSpec: /actuator/health, liveness and readiness all
  UP (readiness against Testcontainers PostgreSQL), and env/metrics stay closed
  - the same readiness endpoint the Compose HEALTHCHECK polls

Assisted-by: claude-code:claude-opus-4-7
Copilot AI review requested due to automatic review settings May 28, 2026 21:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds comprehensive layered test coverage (unit, integration, functional/Geb) for the nine Grails 8 (v8) sample-app guides, mirroring fixes into the vendored snippets/ trees and adding a new Testing chapter per guide. Several guides also receive functional fixes (route mappings, controller/view wiring, BootStrap transactions, gson view model) so the documented sample apps actually compile, run, and serve their endpoints correctly.

Changes:

  • New testing.adoc chapters (and supporting BookSpec/*IntegrationSpec/*FunctionalSpec snippets) for each of the 9 v8 guides, registered in conf/guides.yml.
  • Source/snippet fixes uncovered by the new tests: HTMX createLink(uri:) workarounds, SPA serving via ResourceLoader + new AssetController, gson view fix, custom-widget GSP \\d pattern escape, domainClass.javaClass.simpleName, BootStrap transactions, multi-module UrlMappings + admin REST mapping, MessageSource for bulkCreate errors, etc.
  • Minor guide prose updates explaining the fixes (urlMappings notes, spaController rewrite, bootstrapData note, integrationTest/functionalTest stage wiring, JaCoCo layout.buildDirectory migration).

Reviewed changes

Copilot reviewed 73 out of 73 changed files in this pull request and generated no comments.

Show a summary per file
File Description
guides/grails-vite-spa/v8/snippets/backend/** New SPA AssetController, SpaController rewrite, gson view fix, unit/integration/functional specs.
guides/grails-vite-spa/v8/guide/{urlMappings,spaController,testing}.adoc Prose updates and new testing chapter.
guides/grails-tailwindcss/v8/** New Geb functional spec + testing chapter.
guides/grails-spock-test-tour/v8/** Expanded unit/controller/integration/functional specs; new GSP/BootStrap/UrlMappings snippets; BookController HTML format.
guides/grails-rest-library/v8/** Removed Logger from BootStrap, MessageSource for errors, new layered specs + testing chapter, test-profile yml.
guides/grails-multi-module/v8/** New shared-core unit specs, admin UrlMappings, admin/customer functional specs, testing chapter.
guides/grails-htmx/v8/** createLink(uri:) workaround in 4 GSPs, new Task domain/integration/Geb specs, urlMappings note, testing chapter.
guides/grails-github-actions-cicd/v8/** New Message domain, layered specs, unit/integration/functional chapter additions.
guides/grails-fields-custom-widgets-and-wrappers/v8/** javaClass.simpleName fix, ISBN \\d escape, transactional BootStrap, layered specs + testing chapter.
guides/grails-docker-bootbuildimage/v8/** Actuator health functional spec + testing chapter + test-profile yml.
conf/guides.yml Registers new testing toc entry per touched guide and retitles vite-spa spaController chapter.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@jamesfredley
jamesfredley merged commit 2f04b10 into master May 28, 2026
2 checks passed
@jamesfredley
jamesfredley deleted the add-comprehensive-tests-v8-guides branch May 28, 2026 23:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants