springboot-verification

Verification loop for Spring Boot projects: build, static analysis, tests with coverage, security scans, and diff review before release or PR.

INSTALLATION
npx skills add https://github.com/affaan-m/ecc --skill springboot-verification
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

Spring Boot Verification Loop

Run before PRs, after major changes, and pre-deploy.

When to Activate

  • Before opening a pull request for a Spring Boot service
  • After major refactoring or dependency upgrades
  • Pre-deployment verification for staging or production
  • Running full build → lint → test → security scan pipeline
  • Validating test coverage meets thresholds

Phase 1: Build

mvn -T 4 clean verify -DskipTests

# or

./gradlew clean assemble -x test

If build fails, stop and fix.

Phase 2: Static Analysis

Maven (common plugins):

mvn -T 4 spotbugs:check pmd:check checkstyle:check

Gradle (if configured):

./gradlew checkstyleMain pmdMain spotbugsMain

Phase 3: Tests + Coverage

mvn -T 4 test

mvn jacoco:report   # verify 80%+ coverage

# or

./gradlew test jacocoTestReport

Report:

  • Total tests, passed/failed
  • Coverage % (lines/branches)

Unit Tests

Test service logic in isolation with mocked dependencies:

@ExtendWith(MockitoExtension.class)

class UserServiceTest {

  @Mock private UserRepository userRepository;

  @InjectMocks private UserService userService;

  @Test

  void createUser_validInput_returnsUser() {

    var dto = new CreateUserDto("Alice", "alice@example.com");

    var expected = new User(1L, "Alice", "alice@example.com");

    when(userRepository.save(any(User.class))).thenReturn(expected);

    var result = userService.create(dto);

    assertThat(result.name()).isEqualTo("Alice");

    verify(userRepository).save(any(User.class));

  }

  @Test

  void createUser_duplicateEmail_throwsException() {

    var dto = new CreateUserDto("Alice", "existing@example.com");

    when(userRepository.existsByEmail(dto.email())).thenReturn(true);

    assertThatThrownBy(() -> userService.create(dto))

        .isInstanceOf(DuplicateEmailException.class);

  }

}

Integration Tests with Testcontainers

Test against a real database instead of H2:

@SpringBootTest

@Testcontainers

class UserRepositoryIntegrationTest {

  @Container

  static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine")

      .withDatabaseName("testdb");

  @DynamicPropertySource

  static void configureProperties(DynamicPropertyRegistry registry) {

    registry.add("spring.datasource.url", postgres::getJdbcUrl);

    registry.add("spring.datasource.username", postgres::getUsername);

    registry.add("spring.datasource.password", postgres::getPassword);

  }

  @Autowired private UserRepository userRepository;

  @Test

  void findByEmail_existingUser_returnsUser() {

    userRepository.save(new User("Alice", "alice@example.com"));

    var found = userRepository.findByEmail("alice@example.com");

    assertThat(found).isPresent();

    assertThat(found.get().getName()).isEqualTo("Alice");

  }

}

API Tests with MockMvc

Test controller layer with full Spring context:

@WebMvcTest(UserController.class)

class UserControllerTest {

  @Autowired private MockMvc mockMvc;

  @MockBean private UserService userService;

  @Test

  void createUser_validInput_returns201() throws Exception {

    var user = new UserDto(1L, "Alice", "alice@example.com");

    when(userService.create(any())).thenReturn(user);

    mockMvc.perform(post("/api/users")

            .contentType(MediaType.APPLICATION_JSON)

            .content("""

                {"name": "Alice", "email": "alice@example.com"}

                """))

        .andExpect(status().isCreated())

        .andExpect(jsonPath("$.name").value("Alice"));

  }

  @Test

  void createUser_invalidEmail_returns400() throws Exception {

    mockMvc.perform(post("/api/users")

            .contentType(MediaType.APPLICATION_JSON)

            .content("""

                {"name": "Alice", "email": "not-an-email"}

                """))

        .andExpect(status().isBadRequest());

  }

}

Phase 4: Security Scan

# Dependency CVEs

mvn org.owasp:dependency-check-maven:check

# or

./gradlew dependencyCheckAnalyze

# Secrets in source

grep -rn "password\s*=\s*\"" src/ --include="*.java" --include="*.yml" --include="*.properties"

grep -rn "sk-\|api_key\|secret" src/ --include="*.java" --include="*.yml"

# Secrets (git history)

git secrets --scan  # if configured

Common Security Findings

# Check for System.out.println (use logger instead)

grep -rn "System\.out\.print" src/main/ --include="*.java"

# Check for raw exception messages in responses

grep -rn "e\.getMessage()" src/main/ --include="*.java"

# Check for wildcard CORS

grep -rn "allowedOrigins.*\*" src/main/ --include="*.java"

Phase 5: Lint/Format (optional gate)

mvn spotless:apply   # if using Spotless plugin

./gradlew spotlessApply

Phase 6: Diff Review

git diff --stat

git diff

Checklist:

  • No debugging logs left (System.out, log.debug without guards)
  • Meaningful errors and HTTP statuses
  • Transactions and validation present where needed
  • Config changes documented

Output Template

VERIFICATION REPORT

===================

Build:     [PASS/FAIL]

Static:    [PASS/FAIL] (spotbugs/pmd/checkstyle)

Tests:     [PASS/FAIL] (X/Y passed, Z% coverage)

Security:  [PASS/FAIL] (CVE findings: N)

Diff:      [X files changed]

Overall:   [READY / NOT READY]

Issues to Fix:

1. ...

2. ...

Continuous Mode

  • Re-run phases on significant changes or every 30–60 minutes in long sessions
  • Keep a short loop: mvn -T 4 test + spotbugs for quick feedback

Remember: Fast feedback beats late surprises. Keep the gate strict—treat warnings as defects in production systems.

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card