Lab 01 (Spring Boot Track) β Defect Report
Lab: lab-01-tdd-with-copilot.md
Track affected: π© Spring Boot only (.NET and Python tracks unaffected)
Date: 2026-09-21
Status: Open β code workarounds applied, lab document not yet corrected
Summary
Working through the Spring Boot track end to end surfaced four defects in the lab
document. Two are blocking: following the lab literally produces code that does not
compile, and separately produces code that breaks the Spring context and takes down an
unrelated test suite.
The reference implementation in src-springboot/ is green (135 tests, BUILD SUCCESS),
but only because we deviated from the lab in the ways described below.
| # |
Severity |
Defect |
Participant impact |
| 1 |
π΄ Blocking |
Sample test and sample implementation contradict each other |
Code does not compile |
| 2 |
π΄ Blocking |
@Service + injected Logger breaks the Spring context |
8 unrelated API tests fail |
| 3 |
π‘ Moderate |
Async vs. synchronous guidance contradicts itself |
Participants cannot self-verify |
| 4 |
π‘ Moderate |
Orphaned duplicate content block |
Broken rendering, duplicate headings |
| 5 |
π’ Minor |
Red-phase command fails for the wrong reason |
Confusing error, wrong lesson |
Defect 1 β Sample test and sample implementation are mutually incompatible
Severity: π΄ Blocking
Where
- Β§2.2 "Spring Boot Test Structure" β sample test class
- Β§3.2 "Spring Boot Implementation" β sample implementation
What
The sample test constructs the service with an injected logger:
@Mock
private Logger logger;
@BeforeEach
void setUp() {
service = new NotificationServiceImpl(logger); // constructor takes a Logger
}
The sample implementation declares a static logger and no such constructor:
private static final Logger log = LoggerFactory.getLogger(NotificationServiceImpl.class);
Why it matters
These cannot both be true. A participant who copies Β§2.2 then Β§3.2 gets a compile error
(constructor NotificationServiceImpl cannot be applied to given types). A static field
also cannot be mocked by Mockito, so the logging assertions in Β§2.2 are unsatisfiable β
and .github/instructions/springboot.instructions.md explicitly forbids PowerMock, which
is the only usual escape hatch.
Proposed fix
Pick one and make both snippets agree.
- Option A (recommended) β constructor-inject
Logger. Keeps Β§2.2 valid, keeps
@RequiredArgsConstructor meaningful, and makes logging behaviour testable. Requires
also fixing Defect 2.
- Option B β use
@Slf4j with a static logger, and rewrite Β§2.2 to drop the logger
mock and assert only on behaviour and exceptions.
Workaround applied
Option A. NotificationServiceImpl takes a final Logger via @RequiredArgsConstructor.
Defect 2 β @Service plus injected Logger breaks the Spring context
Severity: π΄ Blocking
Where
- Β§3.1 Spring Boot prompt β asks for "
@Service annotation, constructor injection with @RequiredArgsConstructor"
- Β§3.3 Spring Boot quality checklist β "No constructor needed β Lombok generates it for
final fields (if any dependencies added)"
What
Following the Β§3.1 prompt exactly β @Service plus a Lombok-generated constructor taking
a Logger β makes Spring try to autowire a Logger bean at startup. No such bean exists:
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'notificationServiceImpl':
Unsatisfied dependency expressed through constructor parameter 0:
No qualifying bean of type 'org.slf4j.Logger' available
Parameter 0 of constructor in
com.example.taskmanager.application.services.NotificationServiceImpl
required a bean of type 'org.slf4j.Logger' that could not be found.
Why it matters
This is the worst kind of workshop failure: the participant's own unit tests stay green,
but the failure lands in a module the lab never mentions. All 8 tests in
TaskApiIntegrationTest (taskmanager-api) error out at context initialisation. Without
guidance, participants will not connect the API failure to the service they just wrote.
The Β§3.3 checklist compounds this by asserting that no constructor is needed. The
parenthetical "(if any dependencies added)" describes precisely the case that breaks,
because Logger is not a Spring bean.
Proposed fix
Whichever option is chosen for Defect 1, the lab must say how the bean gets its logger.
Three viable routes:
- Add a no-arg constructor supplying a default logger, so Spring can instantiate the bean
while tests keep using the injected one.
- Drop
@Service and register the bean explicitly in a @Configuration class.
- Use
@Slf4j (Option B above), which sidesteps injection entirely.
Whichever is chosen, Β§3.3 should replace the "No constructor needed" bullet with an
explicit note that Logger is not an injectable bean.
Workaround applied
Route 1 β an explicit no-arg constructor delegating to
LoggerFactory.getLogger(NotificationServiceImpl.class). Spring prefers it because
neither constructor is annotated @Autowired; tests keep the injected constructor.
Defect 3 β Async vs. synchronous guidance contradicts itself
Severity: π‘ Moderate
Where
Four places disagree about whether the interface is async:
| Location |
Says |
| Β§1.2 Spring Boot prompt |
"Use ... CompletableFuture for async operations" |
| Β§1.3 sample interface |
void sendEmailNotification(...) β synchronous |
| Β§1.3 note |
"Spring Boot applications typically use synchronous methods" |
| Β§1.4 verification checklist |
"Spring Boot: Synchronous methods (Spring handles threading)" |
Why it matters
Β§1.4 is a self-check step. A participant who follows the Β§1.2 prompt correctly produces
CompletableFuture<Void> methods, then reaches Β§1.4 and is told their work is wrong.
The design question also cascades: with CompletableFuture, does invalid input throw
synchronously or return a failed future? The lab never says, so Step 2 test design is
underspecified.
Proposed fix
Decide the track's stance and align all four locations. If async is kept, Β§1.4 should read
"Async methods returning CompletableFuture<Void>", and a sentence should be added stating
that guard clauses throw eagerly, before any future is returned.
Workaround applied
Followed the Β§1.2 prompt (async). Guard clauses throw IllegalArgumentException
synchronously so invalid calls fail fast rather than completing exceptionally.
Defect 4 β Orphaned duplicate content block
Severity: π‘ Moderate
Where
Roughly lines 568β700, between the multi-stack Β§2.5 and ## Step 3.
What
Immediately after Β§2.5 "Reflect on Test Design", two stray tree-diagram lines and an
unopened closing code fence appear:
- **Python**: Test modules with shared fixtures where helpful
βββ SendSmsNotificationAsyncTests.cs
βββ SendNotificationAsyncTests.cs
Everything following β a checklist referencing FakeItEasy, plus a second Β§2.3, Β§2.4 and
Β§2.5 β is leftover content from the older .NET-only version of the lab.
Why it matters
- The unmatched fence corrupts Markdown rendering from that point onward.
- The document contains two Β§2.3s, two Β§2.4s and two Β§2.5s, so section references are ambiguous.
- The duplicated Β§2.4 tells every participant to run
dotnet test, including the Spring Boot and Python tracks.
- The duplicated checklist demands "FakeItEasy mocks for
ILogger<NotificationService>", which is meaningless outside .NET.
Proposed fix
Delete the orphaned block from the stray βββ line through the end of the duplicated Β§2.5,
stopping just before ## Step 3. The multi-stack Β§2.3βΒ§2.5 above it already cover the same
ground for all three tracks.
Defect 5 β Red-phase command fails for the wrong reason
Severity: π’ Minor
Where
Β§2.4 "Run Tests and Verify They Fail" β Spring Boot subsection.
What
The lab gives:
cd src-springboot
mvn test -Dtest=NotificationServiceImplTest
In this repository that aborts in the domain module, which contains no matching test:
[ERROR] Failed to execute goal ...surefire-plugin:3.2.5:test (default-test)
on project taskmanager-domain: No tests matching pattern
"NotificationServiceImplTest" were executed!
Why it matters
The lab promises a cannot find symbol: class NotificationServiceImpl compile error, which
is the actual teaching moment. Participants instead get a Surefire configuration error in an
unrelated module and may conclude their setup is broken.
On Windows there is a second trap: PowerShell splits unquoted -D properties at the dot,
yielding Unknown lifecycle phase ".failIfNoSpecifiedTests=false".
Proposed fix
Publish a command that works, with a Windows note:
# macOS / Linux
mvn test -Dtest=NotificationServiceImplTest -Dsurefire.failIfNoSpecifiedTests=false
# Windows PowerShell β quotes are required
mvn test "-Dtest=NotificationServiceImplTest" "-Dsurefire.failIfNoSpecifiedTests=false"
Verification status of the reference implementation
Despite the above, src-springboot/ is green after mvn clean test:
| Module |
Tests |
Result |
| taskmanager-domain |
36 |
β
|
| taskmanager-application |
66 |
β
(33 notification + 33 existing) |
| taskmanager-infrastructure |
25 |
β
|
| taskmanager-api |
8 |
β
|
| Total |
135 |
BUILD SUCCESS |
Files produced:
Two further deliberate deviations
Not defects, but worth flagging so reviewers do not read them as mistakes:
final class β the repo instructions say "make classes final by default"; the
lab's sample omits it. Safe here because there is no @Transactional, hence no CGLIB
proxying (unlike TaskService, which documents that exemption in a comment).
- No
Thread.sleep(100) simulation β the lab's sample sleeps on every call. Omitted
because the lab's own troubleshooting section says "Remove timing dependencies", and
33 tests Γ 100 ms adds wall-clock time without adding a single assertion.
Suggested triage
Before the next workshop run β Defects 1 and 2 must be fixed; participants cannot
complete the Spring Boot track by following the document as written.
Soon after β Defects 3 and 4 make the lab self-contradictory and hard to render.
When convenient β Defect 5 is a one-line command correction plus a Windows note.
Lab 01 (Spring Boot Track) β Defect Report
Lab: lab-01-tdd-with-copilot.md
Track affected: π© Spring Boot only (.NET and Python tracks unaffected)
Date: 2026-09-21
Status: Open β code workarounds applied, lab document not yet corrected
Summary
Working through the Spring Boot track end to end surfaced four defects in the lab
document. Two are blocking: following the lab literally produces code that does not
compile, and separately produces code that breaks the Spring context and takes down an
unrelated test suite.
The reference implementation in
src-springboot/is green (135 tests,BUILD SUCCESS),but only because we deviated from the lab in the ways described below.
@Service+ injectedLoggerbreaks the Spring contextDefect 1 β Sample test and sample implementation are mutually incompatible
Severity: π΄ Blocking
Where
What
The sample test constructs the service with an injected logger:
The sample implementation declares a static logger and no such constructor:
Why it matters
These cannot both be true. A participant who copies Β§2.2 then Β§3.2 gets a compile error
(
constructor NotificationServiceImpl cannot be applied to given types). A static fieldalso cannot be mocked by Mockito, so the logging assertions in Β§2.2 are unsatisfiable β
and
.github/instructions/springboot.instructions.mdexplicitly forbids PowerMock, whichis the only usual escape hatch.
Proposed fix
Pick one and make both snippets agree.
Logger. Keeps Β§2.2 valid, keeps@RequiredArgsConstructormeaningful, and makes logging behaviour testable. Requiresalso fixing Defect 2.
@Slf4jwith a static logger, and rewrite Β§2.2 to drop the loggermock and assert only on behaviour and exceptions.
Workaround applied
Option A.
NotificationServiceImpltakes afinal Loggervia@RequiredArgsConstructor.Defect 2 β
@Serviceplus injectedLoggerbreaks the Spring contextSeverity: π΄ Blocking
Where
@Serviceannotation, constructor injection with@RequiredArgsConstructor"finalfields (if any dependencies added)"What
Following the Β§3.1 prompt exactly β
@Serviceplus a Lombok-generated constructor takinga
Loggerβ makes Spring try to autowire aLoggerbean at startup. No such bean exists:Why it matters
This is the worst kind of workshop failure: the participant's own unit tests stay green,
but the failure lands in a module the lab never mentions. All 8 tests in
TaskApiIntegrationTest(taskmanager-api) error out at context initialisation. Withoutguidance, participants will not connect the API failure to the service they just wrote.
The Β§3.3 checklist compounds this by asserting that no constructor is needed. The
parenthetical "(if any dependencies added)" describes precisely the case that breaks,
because
Loggeris not a Spring bean.Proposed fix
Whichever option is chosen for Defect 1, the lab must say how the bean gets its logger.
Three viable routes:
while tests keep using the injected one.
@Serviceand register the bean explicitly in a@Configurationclass.@Slf4j(Option B above), which sidesteps injection entirely.Whichever is chosen, Β§3.3 should replace the "No constructor needed" bullet with an
explicit note that
Loggeris not an injectable bean.Workaround applied
Route 1 β an explicit no-arg constructor delegating to
LoggerFactory.getLogger(NotificationServiceImpl.class). Spring prefers it becauseneither constructor is annotated
@Autowired; tests keep the injected constructor.Defect 3 β Async vs. synchronous guidance contradicts itself
Severity: π‘ Moderate
Where
Four places disagree about whether the interface is async:
CompletableFuturefor async operations"void sendEmailNotification(...)β synchronousWhy it matters
Β§1.4 is a self-check step. A participant who follows the Β§1.2 prompt correctly produces
CompletableFuture<Void>methods, then reaches Β§1.4 and is told their work is wrong.The design question also cascades: with
CompletableFuture, does invalid input throwsynchronously or return a failed future? The lab never says, so Step 2 test design is
underspecified.
Proposed fix
Decide the track's stance and align all four locations. If async is kept, Β§1.4 should read
"Async methods returning
CompletableFuture<Void>", and a sentence should be added statingthat guard clauses throw eagerly, before any future is returned.
Workaround applied
Followed the Β§1.2 prompt (async). Guard clauses throw
IllegalArgumentExceptionsynchronously so invalid calls fail fast rather than completing exceptionally.
Defect 4 β Orphaned duplicate content block
Severity: π‘ Moderate
Where
Roughly lines 568β700, between the multi-stack Β§2.5 and
## Step 3.What
Immediately after Β§2.5 "Reflect on Test Design", two stray tree-diagram lines and an
unopened closing code fence appear:
Everything following β a checklist referencing FakeItEasy, plus a second Β§2.3, Β§2.4 and
Β§2.5 β is leftover content from the older .NET-only version of the lab.
Why it matters
dotnet test, including the Spring Boot and Python tracks.ILogger<NotificationService>", which is meaningless outside .NET.Proposed fix
Delete the orphaned block from the stray
βββline through the end of the duplicated Β§2.5,stopping just before
## Step 3. The multi-stack Β§2.3βΒ§2.5 above it already cover the sameground for all three tracks.
Defect 5 β Red-phase command fails for the wrong reason
Severity: π’ Minor
Where
Β§2.4 "Run Tests and Verify They Fail" β Spring Boot subsection.
What
The lab gives:
In this repository that aborts in the domain module, which contains no matching test:
Why it matters
The lab promises a
cannot find symbol: class NotificationServiceImplcompile error, whichis the actual teaching moment. Participants instead get a Surefire configuration error in an
unrelated module and may conclude their setup is broken.
On Windows there is a second trap: PowerShell splits unquoted
-Dproperties at the dot,yielding
Unknown lifecycle phase ".failIfNoSpecifiedTests=false".Proposed fix
Publish a command that works, with a Windows note:
Verification status of the reference implementation
Despite the above,
src-springboot/is green aftermvn clean test:Files produced:
Two further deliberate deviations
Not defects, but worth flagging so reviewers do not read them as mistakes:
finalclass β the repo instructions say "make classesfinalby default"; thelab's sample omits it. Safe here because there is no
@Transactional, hence no CGLIBproxying (unlike
TaskService, which documents that exemption in a comment).Thread.sleep(100)simulation β the lab's sample sleeps on every call. Omittedbecause the lab's own troubleshooting section says "Remove timing dependencies", and
33 tests Γ 100 ms adds wall-clock time without adding a single assertion.
Suggested triage
Before the next workshop run β Defects 1 and 2 must be fixed; participants cannot
complete the Spring Boot track by following the document as written.
Soon after β Defects 3 and 4 make the lab self-contradictory and hard to render.
When convenient β Defect 5 is a one-line command correction plus a Windows note.