Skip to content

Lab 01 DefectsΒ #66

Description

@antonyboom

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:

  1. Add a no-arg constructor supplying a default logger, so Spring can instantiate the bean
    while tests keep using the injected one.
  2. Drop @Service and register the bean explicitly in a @Configuration class.
  3. 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:

  1. 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).
  2. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions