mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-10-14 18:18:45 +00:00
feat: Add the implementation of Onion-Architecture (#3532)
* Add the implementation of Onion-Architecture * Execute maven command: mvn spotless:apply
This commit is contained in:
1 parent
b55fc2bcb5
commit
688095a4c7
32 files changed
+3182
-1
No files matched your search
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||
|
||||
The MIT License
|
||||
Copyright © 2014-2022 Ilkka Seppälä
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.iluwatar</groupId>
|
||||
<artifactId>onion-architecture</artifactId>
|
||||
<version>1.26.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>application</artifactId>
|
||||
<name>Application</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.iluwatar</groupId>
|
||||
<artifactId>domain</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JUnit 5 for testing -->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Mockito for mocking -->
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
*
|
||||
* * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||
* *
|
||||
* * The MIT License
|
||||
* * Copyright © 2014-2022 Ilkka Seppälä
|
||||
* *
|
||||
* * Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* * of this software and associated documentation files (the "Software"), to deal
|
||||
* * in the Software without restriction, including without limitation the rights
|
||||
* * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* * copies of the Software, and to permit persons to whom the Software is
|
||||
* * furnished to do so, subject to the following conditions:
|
||||
* *
|
||||
* * The above copyright notice and this permission notice shall be included in
|
||||
* * all copies or substantial portions of the Software.
|
||||
* *
|
||||
* * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* * THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
package com.iluwatar.onion.application.dto;
|
||||
|
||||
public record PersonResponse(
|
||||
Long id,
|
||||
String firstName,
|
||||
String lastName,
|
||||
int age,
|
||||
String phoneNumber,
|
||||
String email,
|
||||
Long categoryId,
|
||||
String categoryType) {}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
*
|
||||
* * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||
* *
|
||||
* * The MIT License
|
||||
* * Copyright © 2014-2022 Ilkka Seppälä
|
||||
* *
|
||||
* * Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* * of this software and associated documentation files (the "Software"), to deal
|
||||
* * in the Software without restriction, including without limitation the rights
|
||||
* * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* * copies of the Software, and to permit persons to whom the Software is
|
||||
* * furnished to do so, subject to the following conditions:
|
||||
* *
|
||||
* * The above copyright notice and this permission notice shall be included in
|
||||
* * all copies or substantial portions of the Software.
|
||||
* *
|
||||
* * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* * THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
package com.iluwatar.onion.application.dto;
|
||||
|
||||
public record SavePersonCommand(
|
||||
String firstName,
|
||||
String lastName,
|
||||
int age,
|
||||
String phoneNumber,
|
||||
String email,
|
||||
String address,
|
||||
Long categoryId,
|
||||
String categoryType) {}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
*
|
||||
* * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||
* *
|
||||
* * The MIT License
|
||||
* * Copyright © 2014-2022 Ilkka Seppälä
|
||||
* *
|
||||
* * Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* * of this software and associated documentation files (the "Software"), to deal
|
||||
* * in the Software without restriction, including without limitation the rights
|
||||
* * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* * copies of the Software, and to permit persons to whom the Software is
|
||||
* * furnished to do so, subject to the following conditions:
|
||||
* *
|
||||
* * The above copyright notice and this permission notice shall be included in
|
||||
* * all copies or substantial portions of the Software.
|
||||
* *
|
||||
* * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* * THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
package com.iluwatar.onion.application.usecase;
|
||||
|
||||
import com.iluwatar.onion.application.dto.PersonResponse;
|
||||
import com.iluwatar.onion.domain.repository.PersonRepository;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class GetPersonUseCase {
|
||||
|
||||
private final PersonRepository repository;
|
||||
|
||||
public GetPersonUseCase(PersonRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public PersonResponse execute(Long id) {
|
||||
var person =
|
||||
repository
|
||||
.findById(id)
|
||||
.orElseThrow(() -> new RuntimeException("Person not found with id: " + id));
|
||||
|
||||
return new PersonResponse(
|
||||
person.getId(),
|
||||
person.getFirstName(),
|
||||
person.getLastName(),
|
||||
person.getAge(),
|
||||
person.getPhoneNumber(),
|
||||
person.getEmail(),
|
||||
person.getCategory().getId(),
|
||||
person.getCategory().getType());
|
||||
}
|
||||
|
||||
public List<PersonResponse> executeAll() {
|
||||
return repository.findAll().stream()
|
||||
.flatMap(Collection::stream)
|
||||
.map(
|
||||
person ->
|
||||
new PersonResponse(
|
||||
person.getId(),
|
||||
person.getFirstName(),
|
||||
person.getLastName(),
|
||||
person.getAge(),
|
||||
person.getPhoneNumber(),
|
||||
person.getEmail(),
|
||||
person.getCategory().getId(),
|
||||
person.getCategory().getType()))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
*
|
||||
* * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||
* *
|
||||
* * The MIT License
|
||||
* * Copyright © 2014-2022 Ilkka Seppälä
|
||||
* *
|
||||
* * Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* * of this software and associated documentation files (the "Software"), to deal
|
||||
* * in the Software without restriction, including without limitation the rights
|
||||
* * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* * copies of the Software, and to permit persons to whom the Software is
|
||||
* * furnished to do so, subject to the following conditions:
|
||||
* *
|
||||
* * The above copyright notice and this permission notice shall be included in
|
||||
* * all copies or substantial portions of the Software.
|
||||
* *
|
||||
* * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* * THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
package com.iluwatar.onion.application.usecase;
|
||||
|
||||
import com.iluwatar.onion.application.dto.PersonResponse;
|
||||
import com.iluwatar.onion.application.dto.SavePersonCommand;
|
||||
import com.iluwatar.onion.domain.model.Category;
|
||||
import com.iluwatar.onion.domain.model.Person;
|
||||
import com.iluwatar.onion.domain.repository.PersonRepository;
|
||||
|
||||
public class SavePersonUseCase {
|
||||
|
||||
private final PersonRepository repository;
|
||||
|
||||
public SavePersonUseCase(PersonRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public PersonResponse execute(SavePersonCommand command) {
|
||||
var category = new Category(command.categoryId(), command.categoryType());
|
||||
var person =
|
||||
new Person(
|
||||
null,
|
||||
command.firstName(),
|
||||
command.lastName(),
|
||||
command.age(),
|
||||
command.phoneNumber(),
|
||||
command.email(),
|
||||
category);
|
||||
|
||||
var savedPerson = repository.save(person);
|
||||
|
||||
return new PersonResponse(
|
||||
savedPerson.getId(),
|
||||
savedPerson.getFirstName(),
|
||||
savedPerson.getLastName(),
|
||||
savedPerson.getAge(),
|
||||
savedPerson.getPhoneNumber(),
|
||||
savedPerson.getEmail(),
|
||||
savedPerson.getCategory().getId(),
|
||||
savedPerson.getCategory().getType());
|
||||
}
|
||||
}
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
*
|
||||
* * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||
* *
|
||||
* * The MIT License
|
||||
* * Copyright © 2014-2022 Ilkka Seppälä
|
||||
* *
|
||||
* * Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* * of this software and associated documentation files (the "Software"), to deal
|
||||
* * in the Software without restriction, including without limitation the rights
|
||||
* * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* * copies of the Software, and to permit persons to whom the Software is
|
||||
* * furnished to do so, subject to the following conditions:
|
||||
* *
|
||||
* * The above copyright notice and this permission notice shall be included in
|
||||
* * all copies or substantial portions of the Software.
|
||||
* *
|
||||
* * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* * THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
package com.iluwatar.onion.application.usecase;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import com.iluwatar.onion.application.dto.PersonResponse;
|
||||
import com.iluwatar.onion.domain.model.Category;
|
||||
import com.iluwatar.onion.domain.model.Person;
|
||||
import com.iluwatar.onion.domain.repository.PersonRepository;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class GetPersonUseCaseTest {
|
||||
|
||||
@Mock private PersonRepository personRepository;
|
||||
|
||||
private GetPersonUseCase getPersonUseCase;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
getPersonUseCase = new GetPersonUseCase(personRepository);
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Execute by ID - Happy Path")
|
||||
class ExecuteByIdHappyPath {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should return PersonResponse when person exists")
|
||||
void shouldReturnPersonResponseWhenPersonExists() {
|
||||
// Arrange
|
||||
var person =
|
||||
new Person(
|
||||
1L,
|
||||
"John",
|
||||
"Doe",
|
||||
25,
|
||||
"+1234567890",
|
||||
"john.doe@example.com",
|
||||
new Category(1L, "Professional"));
|
||||
|
||||
when(personRepository.findById(1L)).thenReturn(Optional.of(person));
|
||||
|
||||
// Act
|
||||
var response = getPersonUseCase.execute(1L);
|
||||
|
||||
// Assert
|
||||
assertNotNull(response);
|
||||
assertEquals(1L, response.id());
|
||||
assertEquals("John", response.firstName());
|
||||
assertEquals("Doe", response.lastName());
|
||||
assertEquals(25, response.age());
|
||||
assertEquals("+1234567890", response.phoneNumber());
|
||||
assertEquals("john.doe@example.com", response.email());
|
||||
assertEquals(1L, response.categoryId());
|
||||
assertEquals("Professional", response.categoryType());
|
||||
|
||||
verify(personRepository, times(1)).findById(1L);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Execute by ID - Error Cases")
|
||||
class ExecuteByIdErrorCases {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should throw RuntimeException when person not found")
|
||||
void shouldThrowExceptionWhenPersonNotFound() {
|
||||
// Arrange
|
||||
when(personRepository.findById(999L)).thenReturn(Optional.empty());
|
||||
|
||||
// Act & Assert
|
||||
var exception = assertThrows(RuntimeException.class, () -> getPersonUseCase.execute(999L));
|
||||
assertTrue(exception.getMessage().contains("Person not found with id: 999"));
|
||||
|
||||
verify(personRepository, times(1)).findById(999L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should propagate exception when repository throws exception")
|
||||
void shouldPropagateExceptionWhenRepositoryFails() {
|
||||
// Arrange
|
||||
when(personRepository.findById(anyLong())).thenThrow(new RuntimeException("Database error"));
|
||||
|
||||
// Act & Assert
|
||||
var exception = assertThrows(RuntimeException.class, () -> getPersonUseCase.execute(1L));
|
||||
assertTrue(exception.getMessage().contains("Database error"));
|
||||
|
||||
verify(personRepository, times(1)).findById(1L);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Execute All - Happy Path")
|
||||
class ExecuteAllHappyPath {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should return list of PersonResponses")
|
||||
void shouldReturnListOfPersonResponses() {
|
||||
// Arrange
|
||||
var person1 =
|
||||
new Person(
|
||||
1L,
|
||||
"John",
|
||||
"Doe",
|
||||
30,
|
||||
"+9876543255",
|
||||
"john.doe@example.com",
|
||||
new Category(2L, "Personal"));
|
||||
|
||||
var person2 =
|
||||
new Person(
|
||||
2L,
|
||||
"Jane",
|
||||
"Smith",
|
||||
25,
|
||||
"+9876543210",
|
||||
"jane.smith@example.com",
|
||||
new Category(2L, "Personal"));
|
||||
|
||||
when(personRepository.findAll()).thenReturn(Optional.of(List.of(person1, person2)));
|
||||
|
||||
// Act
|
||||
var responses = getPersonUseCase.executeAll();
|
||||
|
||||
// Assert
|
||||
assertNotNull(responses);
|
||||
assertEquals(2, responses.size());
|
||||
|
||||
PersonResponse response1 = responses.get(0);
|
||||
assertEquals(1L, response1.id());
|
||||
assertEquals("John", response1.firstName());
|
||||
assertEquals("Doe", response1.lastName());
|
||||
|
||||
PersonResponse response2 = responses.get(1);
|
||||
assertEquals(2L, response2.id());
|
||||
assertEquals("Jane", response2.firstName());
|
||||
assertEquals("Smith", response2.lastName());
|
||||
|
||||
verify(personRepository, times(1)).findAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should return empty list when no persons found")
|
||||
void shouldReturnEmptyListWhenNoPersonsFound() {
|
||||
// Arrange
|
||||
when(personRepository.findAll()).thenReturn(Optional.of(List.of()));
|
||||
|
||||
// Act
|
||||
List<PersonResponse> responses = getPersonUseCase.executeAll();
|
||||
|
||||
// Assert
|
||||
assertNotNull(responses);
|
||||
assertTrue(responses.isEmpty());
|
||||
|
||||
verify(personRepository, times(1)).findAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
+253
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
*
|
||||
* * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||
* *
|
||||
* * The MIT License
|
||||
* * Copyright © 2014-2022 Ilkka Seppälä
|
||||
* *
|
||||
* * Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* * of this software and associated documentation files (the "Software"), to deal
|
||||
* * in the Software without restriction, including without limitation the rights
|
||||
* * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* * copies of the Software, and to permit persons to whom the Software is
|
||||
* * furnished to do so, subject to the following conditions:
|
||||
* *
|
||||
* * The above copyright notice and this permission notice shall be included in
|
||||
* * all copies or substantial portions of the Software.
|
||||
* *
|
||||
* * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* * THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
package com.iluwatar.onion.application.usecase;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import com.iluwatar.onion.application.dto.SavePersonCommand;
|
||||
import com.iluwatar.onion.domain.exception.DomainException;
|
||||
import com.iluwatar.onion.domain.model.Category;
|
||||
import com.iluwatar.onion.domain.model.Person;
|
||||
import com.iluwatar.onion.domain.repository.PersonRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
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;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class SavePersonUseCaseTest {
|
||||
|
||||
@Mock private PersonRepository personRepository;
|
||||
|
||||
@Captor private ArgumentCaptor<Person> personCaptor;
|
||||
|
||||
private SavePersonUseCase savePersonUseCase;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
savePersonUseCase = new SavePersonUseCase(personRepository);
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Execute - Happy Path")
|
||||
class ExecuteHappyPath {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should save person and return PersonResponse")
|
||||
void shouldSavePersonAndReturnResponse() {
|
||||
// Arrange
|
||||
var command =
|
||||
new SavePersonCommand(
|
||||
"John",
|
||||
"Doe",
|
||||
25,
|
||||
"+1234567890",
|
||||
"john.doe@example.com",
|
||||
"123 Main St",
|
||||
1L,
|
||||
"Professional");
|
||||
|
||||
var category = new Category(1L, "Professional");
|
||||
var savedPerson =
|
||||
new Person(
|
||||
100L, // ID generated by database
|
||||
"John",
|
||||
"Doe",
|
||||
25,
|
||||
"+1234567890",
|
||||
"john.doe@example.com",
|
||||
category);
|
||||
|
||||
when(personRepository.save(any(Person.class))).thenReturn(savedPerson);
|
||||
|
||||
// Act
|
||||
var response = savePersonUseCase.execute(command);
|
||||
|
||||
// Assert
|
||||
assertNotNull(response);
|
||||
assertEquals(100L, response.id());
|
||||
assertEquals("John", response.firstName());
|
||||
assertEquals("Doe", response.lastName());
|
||||
assertEquals(25, response.age());
|
||||
assertEquals("+1234567890", response.phoneNumber());
|
||||
assertEquals("john.doe@example.com", response.email());
|
||||
assertEquals(1L, response.categoryId());
|
||||
assertEquals("Professional", response.categoryType());
|
||||
|
||||
// Verify repository interaction
|
||||
verify(personRepository, times(1)).save(any(Person.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should pass correct Person object to repository")
|
||||
void shouldPassCorrectPersonToRepository() {
|
||||
// Arrange
|
||||
var command =
|
||||
new SavePersonCommand(
|
||||
"Jane",
|
||||
"Smith",
|
||||
30,
|
||||
"+9876543210",
|
||||
"jane.smith@example.com",
|
||||
"456 Oak Ave",
|
||||
2L,
|
||||
"Personal");
|
||||
|
||||
var category = new Category(2L, "Personal");
|
||||
var savedPerson =
|
||||
new Person(200L, "Jane", "Smith", 30, "+9876543210", "jane.smith@example.com", category);
|
||||
|
||||
when(personRepository.save(any(Person.class))).thenReturn(savedPerson);
|
||||
|
||||
// Act
|
||||
savePersonUseCase.execute(command);
|
||||
|
||||
// Assert - Verify what was passed to repository
|
||||
verify(personRepository).save(personCaptor.capture());
|
||||
var capturedPerson = personCaptor.getValue();
|
||||
|
||||
assertNull(capturedPerson.getId()); // New person should have null ID
|
||||
assertEquals("Jane", capturedPerson.getFirstName());
|
||||
assertEquals("Smith", capturedPerson.getLastName());
|
||||
assertEquals(30, capturedPerson.getAge());
|
||||
assertEquals("+9876543210", capturedPerson.getPhoneNumber());
|
||||
assertEquals("jane.smith@example.com", capturedPerson.getEmail());
|
||||
assertEquals("Personal", capturedPerson.getCategory().getType());
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Execute - Validation Failures")
|
||||
class ExecuteValidationFailures {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should throw DomainException when age is less than 18")
|
||||
void shouldThrowExceptionWhenAgeIsInvalid() {
|
||||
// Arrange
|
||||
var command =
|
||||
new SavePersonCommand(
|
||||
"Young",
|
||||
"Person",
|
||||
17, // Invalid age
|
||||
"+1234567890",
|
||||
"young@example.com",
|
||||
"123 Main St",
|
||||
1L,
|
||||
"Student");
|
||||
|
||||
// Act & Assert
|
||||
var exception = assertThrows(DomainException.class, () -> savePersonUseCase.execute(command));
|
||||
assertTrue(exception.getMessage().contains("Age cannot be less than 18"));
|
||||
|
||||
// Verify repository was never called
|
||||
verify(personRepository, never()).save(any(Person.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should throw DomainException when email is empty")
|
||||
void shouldThrowExceptionWhenEmailIsEmpty() {
|
||||
// Arrange
|
||||
var command =
|
||||
new SavePersonCommand(
|
||||
"John",
|
||||
"Doe",
|
||||
25,
|
||||
"+1234567890",
|
||||
"", // Empty email
|
||||
"123 Main St",
|
||||
1L,
|
||||
"Professional");
|
||||
|
||||
// Act & Assert
|
||||
var exception = assertThrows(DomainException.class, () -> savePersonUseCase.execute(command));
|
||||
assertTrue(exception.getMessage().contains("Email cannot be null or empty"));
|
||||
|
||||
verify(personRepository, never()).save(any(Person.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should throw DomainException when category type is invalid")
|
||||
void shouldThrowExceptionWhenCategoryTypeIsInvalid() {
|
||||
// Arrange
|
||||
var command =
|
||||
new SavePersonCommand(
|
||||
"John",
|
||||
"Doe",
|
||||
25,
|
||||
"+1234567890",
|
||||
"john@example.com",
|
||||
"123 Main St",
|
||||
1L,
|
||||
"" // Empty category type
|
||||
);
|
||||
|
||||
// Act & Assert
|
||||
var exception = assertThrows(DomainException.class, () -> savePersonUseCase.execute(command));
|
||||
assertTrue(exception.getMessage().contains("Type is null or empty"));
|
||||
|
||||
verify(personRepository, never()).save(any(Person.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Execute - Repository Exceptions")
|
||||
class ExecuteRepositoryExceptions {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should propagate exception when repository fails")
|
||||
void shouldPropagateExceptionWhenRepositoryFails() {
|
||||
// Arrange
|
||||
var command =
|
||||
new SavePersonCommand(
|
||||
"John",
|
||||
"Doe",
|
||||
25,
|
||||
"+1234567890",
|
||||
"john.doe@example.com",
|
||||
"123 Main St",
|
||||
1L,
|
||||
"Professional");
|
||||
|
||||
when(personRepository.save(any(Person.class)))
|
||||
.thenThrow(new RuntimeException("Database connection failed"));
|
||||
|
||||
// Act & Assert
|
||||
var exception =
|
||||
assertThrows(RuntimeException.class, () -> savePersonUseCase.execute(command));
|
||||
assertTrue(exception.getMessage().contains("Database connection failed"));
|
||||
|
||||
verify(personRepository, times(1)).save(any(Person.class));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user