diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 1f0a7b0..879cf89 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -18,7 +18,9 @@ "Bash(git push:*)", "Bash(git commit:*)", "Bash(docker-compose ps:*)", - "Bash(tree:*)" + "Bash(tree:*)", + "Bash(docker-compose:*)", + "Bash(chmod:*)" ], "deny": [], "ask": [] diff --git a/Dockerfile b/Dockerfile index 5c7b6f9..696afc5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,22 @@ # Multi-stage Dockerfile for JAX-WS Hello World Service -# Stage 1: Build the application +# Stage 1: Test the application +FROM maven:3.8.6-openjdk-8 AS tester + +# Set working directory +WORKDIR /app + +# Copy pom.xml and download dependencies (for better caching) +COPY pom.xml . +RUN mvn dependency:go-offline -B + +# Copy source code +COPY src ./src + +# Run tests +RUN mvn test + +# Stage 2: Build the application FROM maven:3.8.6-openjdk-8 AS builder # Set working directory @@ -16,7 +32,7 @@ COPY src ./src # Build the application RUN mvn clean package -DskipTests -# Stage 2: Run the application +# Stage 3: Run the application FROM tomcat:9.0-jdk8 # Remove default Tomcat applications diff --git a/TEST_GUIDE.md b/TEST_GUIDE.md new file mode 100644 index 0000000..81d5390 --- /dev/null +++ b/TEST_GUIDE.md @@ -0,0 +1,346 @@ +# Unit Testing Guide + +This guide explains how to run and write unit tests for the JAX-WS Hello World Service project. + +## Quick Start + +### Run Tests with Docker (Recommended) + +#### Windows +```cmd +run-tests.bat +``` + +#### Linux/Mac +```bash +./run-tests.sh +``` + +### Run Tests with Maven (Local) + +If you have Maven installed locally: + +```bash +mvn test +``` + +## Test Results Summary + +### Current Test Coverage + +| Service | Test Class | Test Cases | Status | +|---------|-----------|------------|--------| +| Hello World Service | HelloWorldServiceImplTest | 10 | ✅ Passing | + +### Test Case Details + +The `HelloWorldServiceImplTest` includes the following test cases: + +1. **testGetHelloWorld_WithValidName** - Verifies greeting with valid name +2. **testGetHelloWorld_WithEmptyString** - Handles empty string input +3. **testGetHelloWorld_WithNull** - Handles null input gracefully +4. **testGetHelloWorld_WithSpecialCharacters** - Handles special characters +5. **testGetHelloWorld_WithSpaces** - Handles names with spaces +6. **testGetHelloWorld_WithLongName** - Handles long names +7. **testGetHelloWorld_ReturnsNonNull** - Ensures non-null response +8. **testGetHelloWorld_StartsWithHelloWorld** - Verifies message format +9. **testGetHelloWorld_EndsWithExclamation** - Verifies message ending +10. **testGetHelloWorld_ContainsName** - Verifies name inclusion + +## Docker Test Configuration + +### Test Architecture + +The project uses a multi-stage Dockerfile: + +- **Stage 1 (tester)**: Runs unit tests +- **Stage 2 (builder)**: Builds the WAR file +- **Stage 3**: Deploys to Tomcat + +### Test Docker Compose + +The `docker-compose.test.yml` file is configured to: +- Target the `tester` stage of the Dockerfile +- Mount source code for live testing +- Cache Maven dependencies for faster subsequent runs +- Set appropriate memory limits + +### Run Tests Manually + +```bash +# Build and run tests +docker-compose -f docker-compose.test.yml up --build + +# Clean up after tests +docker-compose -f docker-compose.test.yml down +``` + +## Testing Framework + +### Dependencies + +The project uses: + +- **JUnit 5 (Jupiter)** - Testing framework + - `junit-jupiter-api:5.9.3` - Test annotations and assertions + - `junit-jupiter-engine:5.9.3` - Test execution engine + +- **Mockito** - Mocking framework (for future complex tests) + - `mockito-core:5.3.1` - Core mocking functionality + - `mockito-junit-jupiter:5.3.1` - JUnit 5 integration + +- **Maven Surefire** - Test runner plugin + - Configured to run all `*Test.java` files + +## Writing New Tests + +### Test Structure + +Tests follow the standard JUnit 5 structure: + +```java +package com.example.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; + +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("Service Tests") +class YourServiceTest { + + private YourService service; + + @BeforeEach + void setUp() { + service = new YourServiceImpl(); + } + + @Test + @DisplayName("Should do something") + void testSomething() { + // Arrange + String input = "test"; + String expected = "expected result"; + + // Act + String result = service.doSomething(input); + + // Assert + assertEquals(expected, result); + } +} +``` + +### Test Best Practices + +1. **Use Descriptive Names**: Use `@DisplayName` for readable test descriptions +2. **Follow AAA Pattern**: Arrange, Act, Assert +3. **Test Edge Cases**: Include null, empty, and boundary conditions +4. **Keep Tests Isolated**: Each test should be independent +5. **Test One Thing**: Each test should verify one specific behavior + +### Adding Tests for New Services + +To add tests for other services (e.g., LoanApprovalService): + +1. Create test class in `src/test/java/com/example/service/` +2. Follow the naming convention: `{ServiceName}Test.java` +3. Run tests to verify + +Example structure for LoanApprovalService tests: + +```java +package com.example.service; + +import com.example.model.*; +import com.example.repository.*; +import org.junit.jupiter.api.*; +import org.mockito.*; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@DisplayName("LoanApprovalService Tests") +class LoanApprovalServiceTest { + + @Mock + private CustomerRepository customerRepository; + + @Mock + private CreditScoreService creditScoreService; + + private LoanApprovalService service; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + service = new LoanApprovalService(); + // Inject mocks if needed + } + + @Test + @DisplayName("Should approve loan for high credit score") + void testLoanApproval_HighCreditScore() { + // Test implementation + } +} +``` + +## Test Directory Structure + +``` +src/ +├── main/ +│ └── java/ +│ └── com/ +│ └── example/ +│ └── service/ +│ ├── HelloWorldService.java +│ └── HelloWorldServiceImpl.java +└── test/ + └── java/ + └── com/ + └── example/ + └── service/ + └── HelloWorldServiceImplTest.java +``` + +## Continuous Integration + +### Running Tests in CI/CD + +The Dockerfile includes testing as the first stage, which means: + +- Tests run automatically during Docker build +- Build fails if tests fail +- Ensures code quality before deployment + +To run tests in CI/CD pipelines: + +```bash +# Option 1: Run full build (includes tests) +docker build -t jaxws-app . + +# Option 2: Run tests only +docker build --target tester -t jaxws-test . + +# Option 3: Use docker-compose +docker-compose -f docker-compose.test.yml up --abort-on-container-exit +``` + +## Viewing Test Reports + +### Console Output + +Test results are displayed in the console with summary: + +``` +------------------------------------------------------- + T E S T S +------------------------------------------------------- +Running com.example.service.HelloWorldServiceImplTest +Tests run: 10, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.024 s + +Results: + +Tests run: 10, Failures: 0, Errors: 0, Skipped: 0 + +BUILD SUCCESS +``` + +### Surefire Reports + +Maven Surefire generates detailed reports in: +- `target/surefire-reports/` (text and XML formats) + +## Troubleshooting + +### Tests Not Running + +**Problem**: Maven doesn't find tests + +**Solution**: +- Ensure test files end with `Test.java` +- Verify test directory structure: `src/test/java/` +- Check pom.xml includes maven-surefire-plugin + +### Docker Build Fails on Tests + +**Problem**: Docker build fails at test stage + +**Solution**: +```bash +# View detailed test output +docker build --target tester -t jaxws-test . + +# Check test container logs +docker logs +``` + +### Dependency Issues + +**Problem**: Cannot resolve JUnit dependencies + +**Solution**: +```bash +# Clear Maven cache and rebuild +docker-compose -f docker-compose.test.yml build --no-cache +``` + +### Permission Denied on run-tests.sh + +**Problem**: Cannot execute run-tests.sh on Linux/Mac + +**Solution**: +```bash +chmod +x run-tests.sh +./run-tests.sh +``` + +## Future Enhancements + +Consider adding tests for: + +1. **LoanApprovalService** + - Customer registration workflows + - Loan approval logic + - Credit score evaluation + - Blacklist handling + +2. **Repository Layer** + - Database operations + - CRUD functionality + - Error handling + +3. **Integration Tests** + - End-to-end SOAP request/response + - Database integration + - Multi-service workflows + +4. **Performance Tests** + - Load testing + - Stress testing + - Response time validation + +## Resources + +- [JUnit 5 User Guide](https://junit.org/junit5/docs/current/user-guide/) +- [Mockito Documentation](https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html) +- [Maven Surefire Plugin](https://maven.apache.org/surefire/maven-surefire-plugin/) +- [Test-Driven Development Best Practices](https://martinfowler.com/bliki/TestDrivenDevelopment.html) + +## Summary + +This project now has a complete unit testing setup with: + +- ✅ JUnit 5 testing framework +- ✅ 10 passing test cases for HelloWorldService +- ✅ Docker-based test execution +- ✅ Convenient test runner scripts +- ✅ Multi-stage Dockerfile with test stage +- ✅ Maven Surefire configuration +- ✅ Mockito for future mocking needs + +To add more tests, simply create new test classes following the established patterns and run them using the provided scripts. diff --git a/docker-compose.test.yml b/docker-compose.test.yml new file mode 100644 index 0000000..c44b993 --- /dev/null +++ b/docker-compose.test.yml @@ -0,0 +1,23 @@ +version: '3.8' + +services: + test: + build: + context: . + dockerfile: Dockerfile + target: tester + image: jaxws-test:latest + container_name: jaxws-unit-tests + volumes: + # Mount source for live testing during development + - ./src:/app/src:ro + - ./pom.xml:/app/pom.xml:ro + # Mount Maven cache to speed up subsequent test runs + - maven-cache:/root/.m2 + command: mvn test + environment: + - MAVEN_OPTS=-Xmx512m + +volumes: + maven-cache: + driver: local diff --git a/pom.xml b/pom.xml index 1a578d9..41e38c9 100644 --- a/pom.xml +++ b/pom.xml @@ -48,6 +48,34 @@ sqlite-jdbc 3.43.0.0 + + + + org.junit.jupiter + junit-jupiter-api + 5.9.3 + test + + + org.junit.jupiter + junit-jupiter-engine + 5.9.3 + test + + + + + org.mockito + mockito-core + 5.3.1 + test + + + org.mockito + mockito-junit-jupiter + 5.3.1 + test + @@ -70,6 +98,16 @@ false + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + + **/*Test.java + + + diff --git a/run-tests.bat b/run-tests.bat new file mode 100644 index 0000000..1442267 --- /dev/null +++ b/run-tests.bat @@ -0,0 +1,20 @@ +@echo off +REM Script to run unit tests using Docker + +echo ================================================================================ +echo Running Unit Tests in Docker +echo ================================================================================ +echo. + +docker-compose -f docker-compose.test.yml up --build + +echo. +echo ================================================================================ +echo Test execution completed +echo ================================================================================ +echo. + +REM Clean up the test container +docker-compose -f docker-compose.test.yml down + +pause diff --git a/run-tests.sh b/run-tests.sh new file mode 100644 index 0000000..929236e --- /dev/null +++ b/run-tests.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# Script to run unit tests using Docker + +echo "================================================================================" +echo "Running Unit Tests in Docker" +echo "================================================================================" +echo "" + +docker-compose -f docker-compose.test.yml up --build + +echo "" +echo "================================================================================" +echo "Test execution completed" +echo "================================================================================" +echo "" + +# Clean up the test container +docker-compose -f docker-compose.test.yml down diff --git a/src/test/java/com/example/service/HelloWorldServiceImplTest.java b/src/test/java/com/example/service/HelloWorldServiceImplTest.java new file mode 100644 index 0000000..5f997fe --- /dev/null +++ b/src/test/java/com/example/service/HelloWorldServiceImplTest.java @@ -0,0 +1,158 @@ +package com.example.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for HelloWorldServiceImpl + * Tests the basic Hello World API functionality + */ +@DisplayName("HelloWorldServiceImpl Tests") +class HelloWorldServiceImplTest { + + private HelloWorldServiceImpl service; + + @BeforeEach + void setUp() { + service = new HelloWorldServiceImpl(); + } + + @Test + @DisplayName("Should return greeting with valid name") + void testGetHelloWorld_WithValidName() { + // Arrange + String name = "John"; + String expected = "Hello World, John!"; + + // Act + String result = service.getHelloWorld(name); + + // Assert + assertEquals(expected, result); + } + + @Test + @DisplayName("Should return greeting with empty string") + void testGetHelloWorld_WithEmptyString() { + // Arrange + String name = ""; + String expected = "Hello World, !"; + + // Act + String result = service.getHelloWorld(name); + + // Assert + assertEquals(expected, result); + } + + @Test + @DisplayName("Should handle null input gracefully") + void testGetHelloWorld_WithNull() { + // Arrange + String name = null; + String expected = "Hello World, null!"; + + // Act + String result = service.getHelloWorld(name); + + // Assert + assertEquals(expected, result); + } + + @Test + @DisplayName("Should handle special characters in name") + void testGetHelloWorld_WithSpecialCharacters() { + // Arrange + String name = "Alice@123"; + String expected = "Hello World, Alice@123!"; + + // Act + String result = service.getHelloWorld(name); + + // Assert + assertEquals(expected, result); + } + + @Test + @DisplayName("Should handle names with spaces") + void testGetHelloWorld_WithSpaces() { + // Arrange + String name = "John Doe"; + String expected = "Hello World, John Doe!"; + + // Act + String result = service.getHelloWorld(name); + + // Assert + assertEquals(expected, result); + } + + @Test + @DisplayName("Should handle long names") + void testGetHelloWorld_WithLongName() { + // Arrange + String name = "ThisIsAVeryLongNameForTestingPurposes"; + String expected = "Hello World, ThisIsAVeryLongNameForTestingPurposes!"; + + // Act + String result = service.getHelloWorld(name); + + // Assert + assertEquals(expected, result); + } + + @Test + @DisplayName("Should return non-null result") + void testGetHelloWorld_ReturnsNonNull() { + // Arrange + String name = "TestUser"; + + // Act + String result = service.getHelloWorld(name); + + // Assert + assertNotNull(result); + } + + @Test + @DisplayName("Should start with 'Hello World,'") + void testGetHelloWorld_StartsWithHelloWorld() { + // Arrange + String name = "User"; + + // Act + String result = service.getHelloWorld(name); + + // Assert + assertTrue(result.startsWith("Hello World,")); + } + + @Test + @DisplayName("Should end with exclamation mark") + void testGetHelloWorld_EndsWithExclamation() { + // Arrange + String name = "User"; + + // Act + String result = service.getHelloWorld(name); + + // Assert + assertTrue(result.endsWith("!")); + } + + @Test + @DisplayName("Should contain the provided name in result") + void testGetHelloWorld_ContainsName() { + // Arrange + String name = "TestUser"; + + // Act + String result = service.getHelloWorld(name); + + // Assert + assertTrue(result.contains(name)); + } +}