From 07d3daaa4c63c21b1f8847bcc6834995364bafc9 Mon Sep 17 00:00:00 2001 From: CAPELLAX02 Date: Tue, 24 Dec 2024 14:08:08 +0300 Subject: [PATCH 1/8] Configure PostgreSQL database via Docker & Reconfigure Java SDK and Maven versions & Fresh test starter --- docker-compose.yml | 4 +- pom.xml | 2 + src/main/resources/application.yml | 4 +- .../demo/AbstractTestcontainersTest.java | 25 -- .../com/ntloc/demo/DemoApplicationTests.java | 8 +- .../customer/CustomerIntegrationTest.java | 184 ----------- .../demo/customer/CustomerRepositoryTest.java | 53 ---- .../demo/customer/CustomerServiceTest.java | 290 ------------------ 8 files changed, 12 insertions(+), 558 deletions(-) delete mode 100644 src/test/java/com/ntloc/demo/AbstractTestcontainersTest.java delete mode 100644 src/test/java/com/ntloc/demo/customer/CustomerIntegrationTest.java delete mode 100644 src/test/java/com/ntloc/demo/customer/CustomerRepositoryTest.java delete mode 100644 src/test/java/com/ntloc/demo/customer/CustomerServiceTest.java diff --git a/docker-compose.yml b/docker-compose.yml index 3d5a90e..3c71ccd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,8 +3,8 @@ services: container_name: postgresql image: postgres:16.2 environment: - POSTGRES_USER: ntloc - POSTGRES_PASSWORD: password + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres volumes: - postgresql:/var/lib/postgresql/data ports: diff --git a/pom.xml b/pom.xml index 588ef0f..accebb8 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,8 @@ Demo Hello World application for Spring Boot 17 + 22 + 22 diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e6b9347..79217cc 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -5,8 +5,8 @@ spring: name: customer datasource: url: jdbc:postgresql://localhost:5432/customer - username: ntloc - password: password + username: postgres + password: postgres jpa: hibernate: ddl-auto: create-drop diff --git a/src/test/java/com/ntloc/demo/AbstractTestcontainersTest.java b/src/test/java/com/ntloc/demo/AbstractTestcontainersTest.java deleted file mode 100644 index 85db7d1..0000000 --- a/src/test/java/com/ntloc/demo/AbstractTestcontainersTest.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.ntloc.demo; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.testcontainers.containers.PostgreSQLContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; -import org.testcontainers.utility.DockerImageName; - -import static org.assertj.core.api.Assertions.assertThat; - -@Testcontainers -public abstract class AbstractTestcontainersTest { - - @Container - @ServiceConnection - static PostgreSQLContainer postgreSQLContainer - = new PostgreSQLContainer<>(DockerImageName.parse("postgres:16.2")); - - @Test - void canEstablishedConnection() { - assertThat(postgreSQLContainer.isCreated()).isTrue(); - assertThat(postgreSQLContainer.isRunning()).isTrue(); - } -} diff --git a/src/test/java/com/ntloc/demo/DemoApplicationTests.java b/src/test/java/com/ntloc/demo/DemoApplicationTests.java index ab270a1..aea249b 100644 --- a/src/test/java/com/ntloc/demo/DemoApplicationTests.java +++ b/src/test/java/com/ntloc/demo/DemoApplicationTests.java @@ -1,10 +1,14 @@ package com.ntloc.demo; -//@SpringBootTest +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest class DemoApplicationTests { - // @Test + @Test void contextLoads() { + System.out.println("Tests starter"); } } diff --git a/src/test/java/com/ntloc/demo/customer/CustomerIntegrationTest.java b/src/test/java/com/ntloc/demo/customer/CustomerIntegrationTest.java deleted file mode 100644 index c85c89b..0000000 --- a/src/test/java/com/ntloc/demo/customer/CustomerIntegrationTest.java +++ /dev/null @@ -1,184 +0,0 @@ -package com.ntloc.demo.customer; - -import com.ntloc.demo.AbstractTestcontainersTest; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.testcontainers.junit.jupiter.Testcontainers; - -import java.util.List; -import java.util.Objects; -import java.util.UUID; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.http.HttpMethod.*; - -@Testcontainers -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -class CustomerIntegrationTest extends AbstractTestcontainersTest { - - public static final String API_CUSTOMERS_PATH = "/api/v1/customers"; - - @Autowired - TestRestTemplate testRestTemplate; - - @Test - void shouldCreateCustomer() { - //given - CreateCustomerRequest request = - new CreateCustomerRequest( - "name", - "email" + UUID.randomUUID() + "@gmail.com", //unique - "address" - ); - //when - ResponseEntity createCustomerResponse = testRestTemplate.exchange( - API_CUSTOMERS_PATH, - POST, - new HttpEntity<>(request), - Void.class); - //then - assertThat(createCustomerResponse.getStatusCode()) - .isEqualTo(HttpStatus.OK); - //get all customers request - ResponseEntity> allCustomersResponse = testRestTemplate.exchange( - API_CUSTOMERS_PATH, - GET, - null, - new ParameterizedTypeReference<>() { - } - ); - assertThat(allCustomersResponse.getStatusCode()) - .isEqualTo(HttpStatus.OK); - Customer customerCreated = Objects.requireNonNull(allCustomersResponse.getBody()) - .stream() - .filter(c -> c.getEmail().equals(request.email())) - .findFirst() - .orElseThrow(); - //comparison of customer we created with create customer request - assertThat(customerCreated.getName()).isEqualTo(request.name()); - assertThat(customerCreated.getEmail()).isEqualTo(request.email()); - assertThat(customerCreated.getAddress()).isEqualTo(request.address()); - - - } - - @Test - void shouldUpdateCustomer() { - //given - CreateCustomerRequest request = - new CreateCustomerRequest( - "name", - "email" + UUID.randomUUID() + "@gmail.com", //unique - "address" - ); - ResponseEntity createCustomerResponse = testRestTemplate.exchange( - API_CUSTOMERS_PATH, - POST, - new HttpEntity<>(request), - Void.class); - assertThat(createCustomerResponse.getStatusCode()) - .isEqualTo(HttpStatus.OK); - //get all customers request - ResponseEntity> allCustomersResponse = testRestTemplate.exchange( - API_CUSTOMERS_PATH, - GET, - null, - new ParameterizedTypeReference<>() { - } - ); - assertThat(allCustomersResponse.getStatusCode()) - .isEqualTo(HttpStatus.OK); - - Long id = Objects.requireNonNull(allCustomersResponse.getBody()).stream() - .filter(c -> c.getEmail().equals(request.email())) - .map(Customer::getId) - .findFirst() - .orElseThrow(); - String newEmail = "newEmail" + UUID.randomUUID() + "@gmail.com"; - //when - testRestTemplate.exchange( - API_CUSTOMERS_PATH + "/" + id + "?email=" + newEmail, - PUT, - null, - Void.class) - .getStatusCode().is2xxSuccessful(); - //getCustomerById after we updated - ResponseEntity customerByIdResponse = testRestTemplate.exchange( - API_CUSTOMERS_PATH + "/" + id, - GET, - null, - new ParameterizedTypeReference<>() { - } - ); - assertThat(customerByIdResponse.getStatusCode()) - .isEqualTo(HttpStatus.OK); - //Do the comparison customer updated with new email we want to update - Customer customerUpdated = Objects.requireNonNull(customerByIdResponse.getBody()); - - assertThat(customerUpdated.getName()).isEqualTo(request.name()); - assertThat(customerUpdated.getEmail()).isEqualTo(newEmail); - assertThat(customerUpdated.getAddress()).isEqualTo(request.address()); - - - } - - @Test - void shouldDeleteCustomer() { - //given - CreateCustomerRequest request = - new CreateCustomerRequest( - "name", - "email" + UUID.randomUUID() + "@gmail.com", //unique - "address" - ); - ResponseEntity createCustomerResponse = testRestTemplate.exchange( - API_CUSTOMERS_PATH, - POST, - new HttpEntity<>(request), - Void.class); - assertThat(createCustomerResponse.getStatusCode()) - .isEqualTo(HttpStatus.OK); - //get all customers request - ResponseEntity> allCustomersResponse = testRestTemplate.exchange( - API_CUSTOMERS_PATH, - GET, - null, - new ParameterizedTypeReference<>() { - } - ); - assertThat(allCustomersResponse.getStatusCode()) - .isEqualTo(HttpStatus.OK); - - Long id = Objects.requireNonNull(allCustomersResponse.getBody()).stream() - .filter(c -> c.getEmail().equals(request.email())) - .map(Customer::getId) - .findFirst() - .orElseThrow(); - //when - testRestTemplate.exchange( - API_CUSTOMERS_PATH + "/" + id, - DELETE, - null, - Void.class - ).getStatusCode().is2xxSuccessful(); - //then - //getCustomerById after we deleted that customer - ResponseEntity customerByIdResponse = testRestTemplate.exchange( - API_CUSTOMERS_PATH + "/" + id, - GET, - null, - new ParameterizedTypeReference<>() { - } - ); - - assertThat(customerByIdResponse.getStatusCode()) - .isEqualTo(HttpStatus.NOT_FOUND); - - } -} \ No newline at end of file diff --git a/src/test/java/com/ntloc/demo/customer/CustomerRepositoryTest.java b/src/test/java/com/ntloc/demo/customer/CustomerRepositoryTest.java deleted file mode 100644 index 5df20cc..0000000 --- a/src/test/java/com/ntloc/demo/customer/CustomerRepositoryTest.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.ntloc.demo.customer; - -import com.ntloc.demo.AbstractTestcontainersTest; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; - -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; - -@DataJpaTest -@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -class CustomerRepositoryTest extends AbstractTestcontainersTest { - - @Autowired - CustomerRepository underTest; - - @BeforeEach - void setUp() { - Customer customer = Customer.create( - "leon", - "leon@gmail.com", - "US"); - underTest.save(customer); - } - - @AfterEach - void tearDown() { - underTest.deleteAll(); - } - - @Test - void shouldReturnCustomerWhenFindByEmail() { - //given - //when - Optional customerByEmail = underTest.findByEmail("leon@gmail.com"); - //then - assertThat(customerByEmail).isPresent(); - } - - @Test - void shouldNotFoundCustomerWhenFindByEmailIsNotPresent() { - //given - //when - Optional customerByEmail = underTest.findByEmail("jason@gmail.com"); - //then - assertThat(customerByEmail).isNotPresent(); - } -} \ No newline at end of file diff --git a/src/test/java/com/ntloc/demo/customer/CustomerServiceTest.java b/src/test/java/com/ntloc/demo/customer/CustomerServiceTest.java deleted file mode 100644 index e95b4ee..0000000 --- a/src/test/java/com/ntloc/demo/customer/CustomerServiceTest.java +++ /dev/null @@ -1,290 +0,0 @@ -package com.ntloc.demo.customer; - -import com.ntloc.demo.exception.CustomerEmailUnavailableException; -import com.ntloc.demo.exception.CustomerNotFoundException; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -class CustomerServiceTest { - - CustomerService underTest; - @Mock - CustomerRepository customerRepository; - @Captor - ArgumentCaptor customerArgumentCaptor; - - @BeforeEach - void setUp() { - underTest = new CustomerService(customerRepository); - } - - @Test - void shouldGetAllCustomers() { - //given - //when - underTest.getCustomers(); - //then - verify(customerRepository).findAll(); - } - - @Test - void shouldThrowNotFoundWhenGivenInvalidIDWhileGetCustomerById() { - //given - long id = 5L; - when(customerRepository.findById(anyLong())).thenReturn(Optional.empty()); - //when - //then - assertThatThrownBy(() -> - underTest.getCustomerById(id)) - .isInstanceOf(CustomerNotFoundException.class) - .hasMessage("Customer with id " + id + " doesn't found"); - } - @Test - void shouldGetCustomerById() { - //given - long id = 5L; - String name = "leon"; - String email = "leon@gmail.com"; - String address = "US"; - Customer customer = Customer.create( - id, - name, - email, - address - ); - when(customerRepository.findById(id)).thenReturn(Optional.of(customer)); - //when - Customer customerById = underTest.getCustomerById(id); - //then - assertThat(customerById.getId()).isEqualTo(id); - assertThat(customerById.getName()).isEqualTo(name); - assertThat(customerById.getEmail()).isEqualTo(email); - assertThat(customerById.getAddress()).isEqualTo(address); - } - - - @Test - void shouldCreateCustomer() { - //given - CreateCustomerRequest createCustomerRequest = - new CreateCustomerRequest( - "leon", - "leon@gmail.com", - "US"); - //when - underTest.createCustomer(createCustomerRequest); - //then - verify(customerRepository).save(customerArgumentCaptor.capture()); - Customer customerCaptured = customerArgumentCaptor.getValue(); - - assertThat(customerCaptured.getName()).isEqualTo(createCustomerRequest.name()); - assertThat(customerCaptured.getEmail()).isEqualTo(createCustomerRequest.email()); - assertThat(customerCaptured.getAddress()).isEqualTo(createCustomerRequest.address()); - - } - - @Test - void shouldNotCreateCustomerAndThrowExceptionWhenEmailUnavailable() { - //given - CreateCustomerRequest createCustomerRequest = - new CreateCustomerRequest( - "leon", - "leon@gmail.com", - "US"); - when(customerRepository.findByEmail(anyString())).thenReturn(Optional.of(new Customer())); - //when - //then - assertThatThrownBy(() -> - underTest.createCustomer(createCustomerRequest)) - .isInstanceOf(CustomerEmailUnavailableException.class) - .hasMessage("The email " + createCustomerRequest.email() + " unavailable."); - - } - - @Test - void shouldThrowNotFoundWhenGivenInvalidIDWhileUpdateCustomer() { - //given - long id = 5L; - String name = "leon"; - String email = "leon@gmail.com"; - String address = "US"; - when(customerRepository.findById(id)) - .thenReturn(Optional.empty()); - //when - //then - assertThatThrownBy(() -> - underTest.updateCustomer(id, name, email, address)) - .isInstanceOf(CustomerNotFoundException.class) - .hasMessage("Customer with id " + id + " doesn't found"); - - verify(customerRepository, never()).save(any()); - } - - @Test - void shouldOnlyUpdateCustomerName() { - //given - long id = 5L; - Customer customer = Customer.create( - id, - "leon", - "leon@gmail.com", - "US" - ); - String newName = "leon mark"; - when(customerRepository.findById(id)) - .thenReturn(Optional.of(customer)); - //when - underTest.updateCustomer(id, newName, null, null); - //then - verify(customerRepository).save(customerArgumentCaptor.capture()); - Customer capturedCustomer = customerArgumentCaptor.getValue(); - - assertThat(capturedCustomer.getName()).isEqualTo(newName); - assertThat(capturedCustomer.getEmail()).isEqualTo(customer.getEmail()); - assertThat(capturedCustomer.getAddress()).isEqualTo(customer.getAddress()); - } - - @Test - void shouldThrowEmailUnavailableWhenGivenEmailAlreadyPresentedWhileUpdateCustomer() { - //given - long id = 5L; - Customer customer = Customer.create( - id, - "leon", - "leon@gmail.com", - "US" - ); - String newEmail = "leonaldo@gmail.com"; - when(customerRepository.findById(id)) - .thenReturn(Optional.of(customer)); - when(customerRepository.findByEmail(newEmail)).thenReturn(Optional.of(new Customer())); - //when - //then - assertThatThrownBy(() -> - underTest.updateCustomer(id, null, newEmail, null)) - .isInstanceOf(CustomerEmailUnavailableException.class) - .hasMessage("The email \"" + newEmail + "\" unavailable to update"); - - verify(customerRepository, never()).save(any()); - } - - @Test - void shouldUpdateOnlyCustomerEmail() { - //given - long id = 5L; - Customer customer = Customer.create( - id, - "leon", - "leon@gmail.com", - "US" - ); - String newEmail = "leonaldo@gmail.com"; - when(customerRepository.findById(id)) - .thenReturn(Optional.of(customer)); - //when - underTest.updateCustomer(id, null, newEmail, null); - //then - verify(customerRepository).save(customerArgumentCaptor.capture()); - Customer capturedCustomer = customerArgumentCaptor.getValue(); - - assertThat(capturedCustomer.getName()).isEqualTo(customer.getName()); - assertThat(capturedCustomer.getEmail()).isEqualTo(newEmail); - assertThat(capturedCustomer.getAddress()).isEqualTo(customer.getAddress()); - } - - @Test - void shouldUpdateOnlyCustomerAddress() { - //given - long id = 5L; - Customer customer = Customer.create( - id, - "leon", - "leon@gmail.com", - "US" - ); - String newAddress = "UK"; - when(customerRepository.findById(id)) - .thenReturn(Optional.of(customer)); - //when - underTest.updateCustomer(id, null, null, newAddress); - //then - verify(customerRepository).save(customerArgumentCaptor.capture()); - Customer capturedCustomer = customerArgumentCaptor.getValue(); - - assertThat(capturedCustomer.getName()).isEqualTo(customer.getName()); - assertThat(capturedCustomer.getEmail()).isEqualTo(customer.getEmail()); - assertThat(capturedCustomer.getAddress()).isEqualTo(newAddress); - } - - @Test - void shouldUpdateAllAttributeWhenUpdateCustomer() { - //given - long id = 5L; - Customer customer = Customer.create( - id, - "leon", - "leon@gmail.com", - "US" - ); - String newName = "leonaldo"; - String newEmail = "leonaldo@gmail.com"; - String newAddress = "UK"; - when(customerRepository.findById(id)) - .thenReturn(Optional.of(customer)); - //when - underTest.updateCustomer(id, newName, newEmail, newAddress); - //then - verify(customerRepository).save(customerArgumentCaptor.capture()); - Customer capturedCustomer = customerArgumentCaptor.getValue(); - - assertThat(capturedCustomer.getName()).isEqualTo(newName); - assertThat(capturedCustomer.getEmail()).isEqualTo(newEmail); - assertThat(capturedCustomer.getAddress()).isEqualTo(newAddress); - } - - - @Test - void shouldThrowNotFoundWhenGivenIdDoesNotExistWhileDeleteCustomer() { - //given - long id = 5L; - when(customerRepository.existsById(id)) - .thenReturn(false); - //when - //then - assertThatThrownBy(() -> - underTest.deleteCustomer(id)) - .isInstanceOf(CustomerNotFoundException.class) - .hasMessage("Customer with id " + id + " doesn't exist."); - verify(customerRepository, never()).deleteById(any()); - - } - - @Test - void shouldDeleteCustomer() { - //given - long id = 5L; - when(customerRepository.existsById(id)) - .thenReturn(true); - //when - underTest.deleteCustomer(id); - //then - verify(customerRepository).deleteById(id); - - } - - -} \ No newline at end of file From 0df14d40b6d0115fb3abcead171afdda3410755a Mon Sep 17 00:00:00 2001 From: CAPELLAX02 Date: Tue, 24 Dec 2024 14:36:20 +0300 Subject: [PATCH 2/8] Success the very first test of the application on Customer Repository. Test containers starter --- .../com/ntloc/demo/customer/Customer.java | 4 +++ .../demo/customer/CustomerRepositoryTest.java | 35 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 src/test/java/com/ntloc/demo/customer/CustomerRepositoryTest.java diff --git a/src/main/java/com/ntloc/demo/customer/Customer.java b/src/main/java/com/ntloc/demo/customer/Customer.java index 8e5aa53..f3eb5f6 100644 --- a/src/main/java/com/ntloc/demo/customer/Customer.java +++ b/src/main/java/com/ntloc/demo/customer/Customer.java @@ -2,9 +2,13 @@ import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; @Table @Entity +@Getter +@Setter public class Customer { @Id diff --git a/src/test/java/com/ntloc/demo/customer/CustomerRepositoryTest.java b/src/test/java/com/ntloc/demo/customer/CustomerRepositoryTest.java new file mode 100644 index 0000000..f91b0b0 --- /dev/null +++ b/src/test/java/com/ntloc/demo/customer/CustomerRepositoryTest.java @@ -0,0 +1,35 @@ +package com.ntloc.demo.customer; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +class CustomerRepositoryTest { + + @Autowired + CustomerRepository customerRepository; + + @Test + void shouldReturnCustomerWhenFindByEmail() { + Customer customer = Customer.create( + "Ahmet", + "ahmtatar@gmail.com", + "Groove St." + ); + customerRepository.save(customer); + + Optional retrievedCustomer = customerRepository.findByEmail("ahmtatar@gmail.com"); + + assertTrue(retrievedCustomer.isPresent(), "Retrieved customer should be present"); + Customer actualCustomer = retrievedCustomer.get(); + assertNotNull(actualCustomer, "Retrieved customer should not be null"); + assertEquals(customer.getEmail(), actualCustomer.getEmail(), "Emails should match"); + assertEquals(customer.getName(), actualCustomer.getName(), "Names should match"); + assertEquals(customer.getAddress(), actualCustomer.getAddress(), "Addresses should match"); + } +} \ No newline at end of file From 6d474e246c4a130f0820740c84372067b664e026 Mon Sep 17 00:00:00 2001 From: CAPELLAX02 Date: Tue, 24 Dec 2024 14:44:35 +0300 Subject: [PATCH 3/8] Pass the Test container implementation test --- .../demo/customer/CustomerRepositoryTest.java | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/ntloc/demo/customer/CustomerRepositoryTest.java b/src/test/java/com/ntloc/demo/customer/CustomerRepositoryTest.java index f91b0b0..f169b94 100644 --- a/src/test/java/com/ntloc/demo/customer/CustomerRepositoryTest.java +++ b/src/test/java/com/ntloc/demo/customer/CustomerRepositoryTest.java @@ -2,18 +2,37 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; import java.util.Optional; import static org.junit.jupiter.api.Assertions.*; -@SpringBootTest +@DataJpaTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@Testcontainers class CustomerRepositoryTest { + @Container + @ServiceConnection + static PostgreSQLContainer postgreSQLContainer = + new PostgreSQLContainer<>(DockerImageName.parse("postgres:16.2")); + @Autowired CustomerRepository customerRepository; + @Test + void canEstablishDatabaseContainerConnection() { + assertTrue(postgreSQLContainer.isCreated()); + assertTrue(postgreSQLContainer.isRunning()); + } + @Test void shouldReturnCustomerWhenFindByEmail() { Customer customer = Customer.create( From 97a84a4d19c449ce6cc374bc9de23e3746c6eadc Mon Sep 17 00:00:00 2001 From: CAPELLAX02 Date: Tue, 24 Dec 2024 14:58:26 +0300 Subject: [PATCH 4/8] Finish repository tests --- .../demo/customer/CustomerRepositoryTest.java | 47 +++++++++++++------ 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/src/test/java/com/ntloc/demo/customer/CustomerRepositoryTest.java b/src/test/java/com/ntloc/demo/customer/CustomerRepositoryTest.java index f169b94..1aa49eb 100644 --- a/src/test/java/com/ntloc/demo/customer/CustomerRepositoryTest.java +++ b/src/test/java/com/ntloc/demo/customer/CustomerRepositoryTest.java @@ -1,5 +1,7 @@ package com.ntloc.demo.customer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; @@ -25,30 +27,47 @@ class CustomerRepositoryTest { new PostgreSQLContainer<>(DockerImageName.parse("postgres:16.2")); @Autowired - CustomerRepository customerRepository; + private CustomerRepository customerRepository; - @Test - void canEstablishDatabaseContainerConnection() { - assertTrue(postgreSQLContainer.isCreated()); - assertTrue(postgreSQLContainer.isRunning()); - } + private Customer testCustomer; - @Test - void shouldReturnCustomerWhenFindByEmail() { - Customer customer = Customer.create( + @BeforeEach + void setUp() { + testCustomer = Customer.create( "Ahmet", "ahmtatar@gmail.com", "Groove St." ); - customerRepository.save(customer); + customerRepository.save(testCustomer); + } + + @AfterEach + void tearDown() { + customerRepository.deleteAll(); + } + + @Test + void canEstablishDatabaseContainerConnection() { + assertTrue(postgreSQLContainer.isCreated(), "Database container should be created"); + assertTrue(postgreSQLContainer.isRunning(), "Database container should be running"); + } + @Test + void shouldReturnCustomerWhenFindByEmail() { Optional retrievedCustomer = customerRepository.findByEmail("ahmtatar@gmail.com"); assertTrue(retrievedCustomer.isPresent(), "Retrieved customer should be present"); Customer actualCustomer = retrievedCustomer.get(); assertNotNull(actualCustomer, "Retrieved customer should not be null"); - assertEquals(customer.getEmail(), actualCustomer.getEmail(), "Emails should match"); - assertEquals(customer.getName(), actualCustomer.getName(), "Names should match"); - assertEquals(customer.getAddress(), actualCustomer.getAddress(), "Addresses should match"); + assertEquals(testCustomer.getEmail(), actualCustomer.getEmail(), "Emails should match"); + assertEquals(testCustomer.getName(), actualCustomer.getName(), "Names should match"); + assertEquals(testCustomer.getAddress(), actualCustomer.getAddress(), "Addresses should match"); + } + + @Test + void shouldNotFindCustomerWhenEmailIsNotPresent() { + Optional customerByEmail = customerRepository.findByEmail("somethingelse@gmail.com"); + + assertFalse(customerByEmail.isPresent(), "Customer with the given email should not be present"); } -} \ No newline at end of file +} From 05d8fba6e079687db96440c7be1122029631c73d Mon Sep 17 00:00:00 2001 From: CAPELLAX02 Date: Tue, 24 Dec 2024 15:35:37 +0300 Subject: [PATCH 5/8] Finish service tests --- .../demo/customer/CustomerServiceTest.java | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 src/test/java/com/ntloc/demo/customer/CustomerServiceTest.java diff --git a/src/test/java/com/ntloc/demo/customer/CustomerServiceTest.java b/src/test/java/com/ntloc/demo/customer/CustomerServiceTest.java new file mode 100644 index 0000000..7b8c02d --- /dev/null +++ b/src/test/java/com/ntloc/demo/customer/CustomerServiceTest.java @@ -0,0 +1,158 @@ +package com.ntloc.demo.customer; + +import com.ntloc.demo.exception.CustomerEmailUnavailableException; +import com.ntloc.demo.exception.CustomerNotFoundException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class CustomerServiceTest { + + @InjectMocks + private CustomerService customerService; + + @Mock + private CustomerRepository customerRepository; + + private Customer testCustomer; + + @BeforeEach + void setUp() { + testCustomer = Customer.create( + 1L, + "Leon", + "leon@gmail.com", + "US" + ); + } + + @Test + void shouldGetAllCustomers() { + when(customerRepository.findAll()).thenReturn(List.of(testCustomer)); + + List customers = customerService.getCustomers(); + + assertNotNull(customers, "The customer list should not be null"); + assertEquals(1, customers.size(), "There should be exactly one customer"); + assertEquals(testCustomer, customers.get(0), "The returned customer should match the test data"); + verify(customerRepository, times(1)).findAll(); + } + + @Test + void shouldGetCustomerById() { + long id = 1L; + when(customerRepository.findById(id)).thenReturn(Optional.of(testCustomer)); + + Customer customer = customerService.getCustomerById(id); + + assertNotNull(customer, "The customer should not be null"); + assertEquals(testCustomer, customer, "The returned customer should match the test data"); + verify(customerRepository, times(1)).findById(id); + } + + @Test + void shouldThrowNotFoundWhenCustomerIdIsInvalid() { + long id = 5L; + when(customerRepository.findById(id)).thenReturn(Optional.empty()); + + Exception exception = assertThrows(CustomerNotFoundException.class, () -> customerService.getCustomerById(id)); + assertEquals("Customer with id " + id + " doesn't found", exception.getMessage()); + verify(customerRepository, times(1)).findById(id); + } + + @Test + void shouldCreateCustomer() { + CreateCustomerRequest request = new CreateCustomerRequest( + "Leon", + "leon@gmail.com", + "US" + ); + when(customerRepository.findByEmail(request.email())).thenReturn(Optional.empty()); + + customerService.createCustomer(request); + + ArgumentCaptor customerCaptor = ArgumentCaptor.forClass(Customer.class); + verify(customerRepository).save(customerCaptor.capture()); + Customer capturedCustomer = customerCaptor.getValue(); + assertEquals(request.name(), capturedCustomer.getName(), "The customer name should match"); + assertEquals(request.email(), capturedCustomer.getEmail(), "The customer email should match"); + assertEquals(request.address(), capturedCustomer.getAddress(), "The customer address should match"); + } + + @Test + void shouldThrowExceptionWhenEmailIsUnavailable() { + CreateCustomerRequest request = new CreateCustomerRequest( + "Leon", + "leon@gmail.com", + "US" + ); + when(customerRepository.findByEmail(request.email())).thenReturn(Optional.of(testCustomer)); + + Exception exception = assertThrows(CustomerEmailUnavailableException.class, () -> + customerService.createCustomer(request)); + assertEquals("The email " + request.email() + " unavailable.", exception.getMessage()); + verify(customerRepository, never()).save(any()); + } + + @Test + void shouldUpdateCustomerSuccessfully() { + long id = 1L; + String newName = "Leonardo"; + when(customerRepository.findById(id)).thenReturn(Optional.of(testCustomer)); + + customerService.updateCustomer(id, newName, null, null); + + ArgumentCaptor customerCaptor = ArgumentCaptor.forClass(Customer.class); + verify(customerRepository).save(customerCaptor.capture()); + Customer capturedCustomer = customerCaptor.getValue(); + assertEquals(newName, capturedCustomer.getName(), "The customer name should be updated"); + assertEquals(testCustomer.getEmail(), capturedCustomer.getEmail(), "The customer email should remain the same"); + assertEquals(testCustomer.getAddress(), capturedCustomer.getAddress(), "The customer address should remain the same"); + } + + + @Test + void shouldThrowExceptionWhenUpdatingCustomerWithUnavailableEmail() { + long id = 1L; + String newEmail = "newemail@gmail.com"; + when(customerRepository.findById(id)).thenReturn(Optional.of(testCustomer)); + when(customerRepository.findByEmail(newEmail)).thenReturn(Optional.of(new Customer())); + + Exception exception = assertThrows(CustomerEmailUnavailableException.class, () -> + customerService.updateCustomer(id, null, newEmail, null)); + assertEquals("The email \"" + newEmail + "\" unavailable to update", exception.getMessage()); + verify(customerRepository, never()).save(any()); + } + + @Test + void shouldDeleteCustomerSuccessfully() { + long id = 1L; + when(customerRepository.existsById(id)).thenReturn(true); + + customerService.deleteCustomer(id); + + verify(customerRepository, times(1)).deleteById(id); + } + + @Test + void shouldThrowExceptionWhenDeletingNonExistentCustomer() { + long id = 1L; + when(customerRepository.existsById(id)).thenReturn(false); + + Exception exception = assertThrows(CustomerNotFoundException.class, () -> + customerService.deleteCustomer(id)); + assertEquals("Customer with id " + id + " doesn't exist.", exception.getMessage()); + verify(customerRepository, never()).deleteById(any()); + } +} From 97d11677bb1487ff15e0e8fae53f2aa6fb067846 Mon Sep 17 00:00:00 2001 From: CAPELLAX02 Date: Tue, 24 Dec 2024 15:45:20 +0300 Subject: [PATCH 6/8] Seperate test container configurations due to abstraction. Integration tests starter. --- .../demo/AbstractTestContainersTest.java | 25 ++++++++++++++++ .../customer/CustomerIntegrationTest.java | 30 +++++++++++++++++++ .../demo/customer/CustomerRepositoryTest.java | 20 ++----------- 3 files changed, 57 insertions(+), 18 deletions(-) create mode 100644 src/test/java/com/ntloc/demo/AbstractTestContainersTest.java create mode 100644 src/test/java/com/ntloc/demo/customer/CustomerIntegrationTest.java diff --git a/src/test/java/com/ntloc/demo/AbstractTestContainersTest.java b/src/test/java/com/ntloc/demo/AbstractTestContainersTest.java new file mode 100644 index 0000000..4754840 --- /dev/null +++ b/src/test/java/com/ntloc/demo/AbstractTestContainersTest.java @@ -0,0 +1,25 @@ +package com.ntloc.demo; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Testcontainers +public abstract class AbstractTestContainersTest { + + @Container + @ServiceConnection + static PostgreSQLContainer postgreSQLContainer = + new PostgreSQLContainer<>("postgres:16.2"); + + @Test + void canEstablishDatabaseContainerConnection() { + assertTrue(postgreSQLContainer.isCreated(), "Database container should be created"); + assertTrue(postgreSQLContainer.isRunning(), "Database container should be running"); + } + +} diff --git a/src/test/java/com/ntloc/demo/customer/CustomerIntegrationTest.java b/src/test/java/com/ntloc/demo/customer/CustomerIntegrationTest.java new file mode 100644 index 0000000..12eebd1 --- /dev/null +++ b/src/test/java/com/ntloc/demo/customer/CustomerIntegrationTest.java @@ -0,0 +1,30 @@ +package com.ntloc.demo.customer; + +import com.ntloc.demo.AbstractTestContainersTest; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class CustomerIntegrationTest extends AbstractTestContainersTest { + + static final String CUSTOMER_API_PATH = "/api/v1/customers"; + + @Test + void createCustomer() { + } + + @Test + void updateCustomer() { + } + + @Test + void deleteCustomer() { + } +} \ No newline at end of file diff --git a/src/test/java/com/ntloc/demo/customer/CustomerRepositoryTest.java b/src/test/java/com/ntloc/demo/customer/CustomerRepositoryTest.java index 1aa49eb..c55c324 100644 --- a/src/test/java/com/ntloc/demo/customer/CustomerRepositoryTest.java +++ b/src/test/java/com/ntloc/demo/customer/CustomerRepositoryTest.java @@ -1,16 +1,12 @@ package com.ntloc.demo.customer; +import com.ntloc.demo.AbstractTestContainersTest; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.testcontainers.containers.PostgreSQLContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; -import org.testcontainers.utility.DockerImageName; import java.util.Optional; @@ -18,13 +14,7 @@ @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@Testcontainers -class CustomerRepositoryTest { - - @Container - @ServiceConnection - static PostgreSQLContainer postgreSQLContainer = - new PostgreSQLContainer<>(DockerImageName.parse("postgres:16.2")); +class CustomerRepositoryTest extends AbstractTestContainersTest { @Autowired private CustomerRepository customerRepository; @@ -46,12 +36,6 @@ void tearDown() { customerRepository.deleteAll(); } - @Test - void canEstablishDatabaseContainerConnection() { - assertTrue(postgreSQLContainer.isCreated(), "Database container should be created"); - assertTrue(postgreSQLContainer.isRunning(), "Database container should be running"); - } - @Test void shouldReturnCustomerWhenFindByEmail() { Optional retrievedCustomer = customerRepository.findByEmail("ahmtatar@gmail.com"); From e394cc66260cea8b58cb099df040eb46b2949605 Mon Sep 17 00:00:00 2001 From: CAPELLAX02 Date: Tue, 24 Dec 2024 16:05:00 +0300 Subject: [PATCH 7/8] Complete integration tests on all endpoints & Create test reports using Jacoco --- .../com/ntloc/demo/DemoApplicationTests.java | 6 +- .../customer/CustomerIntegrationTest.java | 154 ++++++++++++++++-- 2 files changed, 147 insertions(+), 13 deletions(-) diff --git a/src/test/java/com/ntloc/demo/DemoApplicationTests.java b/src/test/java/com/ntloc/demo/DemoApplicationTests.java index aea249b..14736b5 100644 --- a/src/test/java/com/ntloc/demo/DemoApplicationTests.java +++ b/src/test/java/com/ntloc/demo/DemoApplicationTests.java @@ -3,12 +3,12 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; -@SpringBootTest +//@SpringBootTest class DemoApplicationTests { - @Test +// @Test void contextLoads() { - System.out.println("Tests starter"); +// System.out.println("Tests starter"); } } diff --git a/src/test/java/com/ntloc/demo/customer/CustomerIntegrationTest.java b/src/test/java/com/ntloc/demo/customer/CustomerIntegrationTest.java index 12eebd1..8de686b 100644 --- a/src/test/java/com/ntloc/demo/customer/CustomerIntegrationTest.java +++ b/src/test/java/com/ntloc/demo/customer/CustomerIntegrationTest.java @@ -2,29 +2,163 @@ import com.ntloc.demo.AbstractTestContainersTest; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.testcontainers.containers.PostgreSQLContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; -import org.testcontainers.utility.DockerImageName; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.*; + +import java.util.UUID; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class CustomerIntegrationTest extends AbstractTestContainersTest { - static final String CUSTOMER_API_PATH = "/api/v1/customers"; + private static final String CUSTOMER_API_PATH = "/api/v1/customers"; + + @Autowired + private TestRestTemplate testRestTemplate; @Test - void createCustomer() { + void shouldCreateCustomer() { + // Arrange + CreateCustomerRequest request = new CreateCustomerRequest( + "John Doe", + "john.doe" + UUID.randomUUID() + "@gmail.com", + "123 Main St" + ); + + // Act + ResponseEntity response = testRestTemplate.exchange( + CUSTOMER_API_PATH, + HttpMethod.POST, + new HttpEntity<>(request), + Void.class + ); + + // Assert + assertEquals(HttpStatus.OK, response.getStatusCode(), "Customer creation should return HTTP 201"); } @Test - void updateCustomer() { + void shouldGetAllCustomers() { + // Arrange + createTestCustomer("Alice"); + createTestCustomer("Bob"); + + // Act + ResponseEntity response = testRestTemplate.exchange( + CUSTOMER_API_PATH, + HttpMethod.GET, + null, + Customer[].class + ); + + // Assert + assertEquals(HttpStatus.OK, response.getStatusCode(), "Get all customers should return HTTP 200"); + assertNotNull(response.getBody(), "Customer list should not be null"); + assertTrue(response.getBody().length >= 2, "Customer list should contain at least two customers"); } @Test - void deleteCustomer() { + void shouldGetCustomerById() { + // Arrange + Long customerId = createAndGetCustomerId("Charlie"); + + // Act + ResponseEntity response = testRestTemplate.exchange( + CUSTOMER_API_PATH + "/" + customerId, + HttpMethod.GET, + null, + Customer.class + ); + + // Assert + assertEquals(HttpStatus.OK, response.getStatusCode(), "Get customer by ID should return HTTP 200"); + assertNotNull(response.getBody(), "Customer should not be null"); + assertEquals("Charlie", response.getBody().getName(), "Customer name should match"); + } + + @Test + void shouldUpdateCustomer() { + // Arrange + Long customerId = createAndGetCustomerId("David"); + String newName = "David Updated"; + String newAddress = "New Address"; + + // Act + ResponseEntity response = testRestTemplate.exchange( + CUSTOMER_API_PATH + "/" + customerId + "?name=" + newName + "&address=" + newAddress, + HttpMethod.PUT, + null, + Void.class + ); + + // Assert + assertEquals(HttpStatus.OK, response.getStatusCode(), "Update customer should return HTTP 204"); + + ResponseEntity updatedCustomerResponse = testRestTemplate.exchange( + CUSTOMER_API_PATH + "/" + customerId, + HttpMethod.GET, + null, + Customer.class + ); + + assertNotNull(updatedCustomerResponse.getBody(), "Updated customer should not be null"); + assertEquals(newName, updatedCustomerResponse.getBody().getName(), "Customer name should be updated"); + assertEquals(newAddress, updatedCustomerResponse.getBody().getAddress(), "Customer address should be updated"); + } + + @Test + void shouldDeleteCustomer() { + // Arrange + Long customerId = createAndGetCustomerId("Eve"); + + // Act + ResponseEntity response = testRestTemplate.exchange( + CUSTOMER_API_PATH + "/" + customerId, + HttpMethod.DELETE, + null, + Void.class + ); + + // Assert + assertEquals(HttpStatus.OK, response.getStatusCode(), "Delete customer should return HTTP 204"); + + ResponseEntity deletedCustomerResponse = testRestTemplate.exchange( + CUSTOMER_API_PATH + "/" + customerId, + HttpMethod.GET, + null, + Customer.class + ); + + assertEquals(HttpStatus.NOT_FOUND, deletedCustomerResponse.getStatusCode(), "Deleted customer should not be found"); + } + + // Helper methods + private void createTestCustomer(String name) { + CreateCustomerRequest request = new CreateCustomerRequest( + name, + name.toLowerCase() + UUID.randomUUID() + "@example.com", + "Test Address" + ); + ResponseEntity response = testRestTemplate.postForEntity(CUSTOMER_API_PATH, request, Void.class); + assertEquals(HttpStatus.OK, response.getStatusCode(), "Customer creation should return HTTP 201"); + } + + private Long createAndGetCustomerId(String name) { + createTestCustomer(name); + + ResponseEntity getAllResponse = testRestTemplate.exchange( + CUSTOMER_API_PATH, + HttpMethod.GET, + null, + Customer[].class + ); + + assertNotNull(getAllResponse.getBody(), "Customer list should not be null"); + assertTrue(getAllResponse.getBody().length > 0, "Customer list should not be empty"); + + return getAllResponse.getBody()[getAllResponse.getBody().length - 1].getId(); } -} \ No newline at end of file +} From 953f83c94567b40d9935fdf96b43402d1e573b67 Mon Sep 17 00:00:00 2001 From: CAPELLAX02 Date: Tue, 24 Dec 2024 16:12:45 +0300 Subject: [PATCH 8/8] Add README.md --- README.md | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 542b8a7..4882ba7 100644 --- a/README.md +++ b/README.md @@ -1 +1,65 @@ -# springboot-demo \ No newline at end of file +# Customer Management Service - Testing + +## Overview + +This project focuses on the implementation of robust and comprehensive testing for a REST API-based Customer Management Service. The testing framework incorporates tools like JUnit, Mockito, Testcontainers, and Jacoco to ensure reliability and maintainability. + +## Testing Tools and Frameworks +- **JUnit 5:** For writing and running unit and integration tests. +- **Mockito:** Used to mock dependencies and isolate the units being tested. +- **Testcontainers:** Enables integration testing with a PostgreSQL database in a containerized environment. +- **Jacoco:** Provides code coverage reporting to ensure the adequacy of test coverage. + +## Key Features of the Testing Suite + +1. **Unit Tests:** + +- Ensure the correctness of service and controller logic. +Dependencies are mocked using Mockito to isolate the unit under test. + +**Integration Tests:** + +- Verify end-to-end functionality of the REST API with a real database (PostgreSQL) using Testcontainers. +Test scenarios include CRUD operations and edge cases. + +3. **Code Coverage:** + +- Jacoco generates detailed coverage reports, ensuring all critical code paths are tested. + +## Running the Tests + +### Prerequisites: + +- Install Java 17+ and Maven 3.6+. +- Ensure Docker is installed and running for Testcontainers. + +#### Run All Tests: + +``` +mvn test +``` +Or you can manually test all the tests for the particular test file in your IDE. + +#### View Code Coverage: + +After running tests, open the generated Jacoco report in the following path: + +```target/site/jacoco/index.html``` + +Navigate to this file in your browser to view detailed coverage metrics. + +#### Example Test Scenarios + +1. **Unit Test** +- **CustomerServiceTest:** +Tests service-layer methods for creating, updating, and deleting customers. +Verifies exceptions like CustomerNotFoundException and CustomerEmailUnavailableException are properly thrown. + +2. **Integration Test** +- **CustomerIntegrationTest**: +Uses Testcontainers to spin up a PostgreSQL container for testing the application in a realistic environment. +Covers scenarios like creating a customer, retrieving by ID, updating, and deleting. + +## Contribution +Feel free to contribute to this project by adding more tests or enhancing existing ones. Open a pull request for any updates. +