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. + 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/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/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 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/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..14736b5 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; +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 index c85c89b..8de686b 100644 --- a/src/test/java/com/ntloc/demo/customer/CustomerIntegrationTest.java +++ b/src/test/java/com/ntloc/demo/customer/CustomerIntegrationTest.java @@ -1,184 +1,164 @@ package com.ntloc.demo.customer; -import com.ntloc.demo.AbstractTestcontainersTest; +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 org.springframework.http.*; + import java.util.UUID; -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.http.HttpMethod.*; +import static org.junit.jupiter.api.Assertions.*; -@Testcontainers @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -class CustomerIntegrationTest extends AbstractTestcontainersTest { +class CustomerIntegrationTest extends AbstractTestContainersTest { - public static final String API_CUSTOMERS_PATH = "/api/v1/customers"; + private static final String CUSTOMER_API_PATH = "/api/v1/customers"; @Autowired - TestRestTemplate testRestTemplate; + private 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, + // 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); - //then - assertThat(createCustomerResponse.getStatusCode()) - .isEqualTo(HttpStatus.OK); - //get all customers request - ResponseEntity> allCustomersResponse = testRestTemplate.exchange( - API_CUSTOMERS_PATH, - GET, - null, - new ParameterizedTypeReference<>() { - } + Void.class ); - 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()); + // Assert + assertEquals(HttpStatus.OK, response.getStatusCode(), "Customer creation should return HTTP 201"); + } + @Test + 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 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, + void shouldGetCustomerById() { + // Arrange + Long customerId = createAndGetCustomerId("Charlie"); + + // Act + ResponseEntity response = testRestTemplate.exchange( + CUSTOMER_API_PATH + "/" + customerId, + HttpMethod.GET, null, - new ParameterizedTypeReference<>() { - } + Customer.class ); - 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, + + // 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, - new ParameterizedTypeReference<>() { - } + Void.class ); - 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()); + // 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() { - //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, + // Arrange + Long customerId = createAndGetCustomerId("Eve"); + + // Act + ResponseEntity response = testRestTemplate.exchange( + CUSTOMER_API_PATH + "/" + customerId, + HttpMethod.DELETE, null, - new ParameterizedTypeReference<>() { - } + Void.class ); - 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, + + // Assert + assertEquals(HttpStatus.OK, response.getStatusCode(), "Delete customer should return HTTP 204"); + + ResponseEntity deletedCustomerResponse = testRestTemplate.exchange( + CUSTOMER_API_PATH + "/" + customerId, + HttpMethod.GET, null, - Void.class - ).getStatusCode().is2xxSuccessful(); - //then - //getCustomerById after we deleted that customer - ResponseEntity customerByIdResponse = testRestTemplate.exchange( - API_CUSTOMERS_PATH + "/" + id, - GET, + 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, - new ParameterizedTypeReference<>() { - } + Customer[].class ); - assertThat(customerByIdResponse.getStatusCode()) - .isEqualTo(HttpStatus.NOT_FOUND); + 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 +} diff --git a/src/test/java/com/ntloc/demo/customer/CustomerRepositoryTest.java b/src/test/java/com/ntloc/demo/customer/CustomerRepositoryTest.java index 5df20cc..c55c324 100644 --- a/src/test/java/com/ntloc/demo/customer/CustomerRepositoryTest.java +++ b/src/test/java/com/ntloc/demo/customer/CustomerRepositoryTest.java @@ -1,6 +1,6 @@ package com.ntloc.demo.customer; -import com.ntloc.demo.AbstractTestcontainersTest; +import com.ntloc.demo.AbstractTestContainersTest; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -10,44 +10,48 @@ import java.util.Optional; -import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -class CustomerRepositoryTest extends AbstractTestcontainersTest { +class CustomerRepositoryTest extends AbstractTestContainersTest { @Autowired - CustomerRepository underTest; + private CustomerRepository customerRepository; + + private Customer testCustomer; @BeforeEach void setUp() { - Customer customer = Customer.create( - "leon", - "leon@gmail.com", - "US"); - underTest.save(customer); + testCustomer = Customer.create( + "Ahmet", + "ahmtatar@gmail.com", + "Groove St." + ); + customerRepository.save(testCustomer); } @AfterEach void tearDown() { - underTest.deleteAll(); + customerRepository.deleteAll(); } @Test void shouldReturnCustomerWhenFindByEmail() { - //given - //when - Optional customerByEmail = underTest.findByEmail("leon@gmail.com"); - //then - assertThat(customerByEmail).isPresent(); + 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(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 shouldNotFoundCustomerWhenFindByEmailIsNotPresent() { - //given - //when - Optional customerByEmail = underTest.findByEmail("jason@gmail.com"); - //then - assertThat(customerByEmail).isNotPresent(); + 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 +} diff --git a/src/test/java/com/ntloc/demo/customer/CustomerServiceTest.java b/src/test/java/com/ntloc/demo/customer/CustomerServiceTest.java index e95b4ee..7b8c02d 100644 --- a/src/test/java/com/ntloc/demo/customer/CustomerServiceTest.java +++ b/src/test/java/com/ntloc/demo/customer/CustomerServiceTest.java @@ -6,285 +6,153 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; -import org.mockito.Captor; +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.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.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class CustomerServiceTest { - CustomerService underTest; + @InjectMocks + private CustomerService customerService; + @Mock - CustomerRepository customerRepository; - @Captor - ArgumentCaptor customerArgumentCaptor; + private CustomerRepository customerRepository; + + private Customer testCustomer; @BeforeEach void setUp() { - underTest = new CustomerService(customerRepository); + testCustomer = Customer.create( + 1L, + "Leon", + "leon@gmail.com", + "US" + ); } @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); - } + when(customerRepository.findAll()).thenReturn(List.of(testCustomer)); + List customers = customerService.getCustomers(); - @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()); - + 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 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."); + 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 shouldThrowNotFoundWhenGivenInvalidIDWhileUpdateCustomer() { - //given + void shouldThrowNotFoundWhenCustomerIdIsInvalid() { 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"); + when(customerRepository.findById(id)).thenReturn(Optional.empty()); - verify(customerRepository, never()).save(any()); + 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 shouldOnlyUpdateCustomerName() { - //given - long id = 5L; - Customer customer = Customer.create( - id, - "leon", + void shouldCreateCustomer() { + CreateCustomerRequest request = new CreateCustomerRequest( + "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()); - } + when(customerRepository.findByEmail(request.email())).thenReturn(Optional.empty()); - @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"); + customerService.createCustomer(request); - verify(customerRepository, never()).save(any()); + 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 shouldUpdateOnlyCustomerEmail() { - //given - long id = 5L; - Customer customer = Customer.create( - id, - "leon", + void shouldThrowExceptionWhenEmailIsUnavailable() { + CreateCustomerRequest request = new CreateCustomerRequest( + "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()); - } + when(customerRepository.findByEmail(request.email())).thenReturn(Optional.of(testCustomer)); - @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); + Exception exception = assertThrows(CustomerEmailUnavailableException.class, () -> + customerService.createCustomer(request)); + assertEquals("The email " + request.email() + " unavailable.", exception.getMessage()); + verify(customerRepository, never()).save(any()); } @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); + 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 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()); + 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 shouldDeleteCustomer() { - //given - long id = 5L; - when(customerRepository.existsById(id)) - .thenReturn(true); - //when - underTest.deleteCustomer(id); - //then - verify(customerRepository).deleteById(id); + 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); -} \ No newline at end of file + Exception exception = assertThrows(CustomerNotFoundException.class, () -> + customerService.deleteCustomer(id)); + assertEquals("Customer with id " + id + " doesn't exist.", exception.getMessage()); + verify(customerRepository, never()).deleteById(any()); + } +}