diff --git a/pom.xml b/pom.xml index 3f603d3..028d5bf 100644 --- a/pom.xml +++ b/pom.xml @@ -75,6 +75,25 @@ + + org.jacoco + jacoco-maven-plugin + 0.8.10 + + + + prepare-agent + + + + report + test + + report + + + + diff --git a/src/test/java/de/eemkeen/MicrostreamApplicationTest.java b/src/test/java/de/eemkeen/MicrostreamApplicationTest.java new file mode 100644 index 0000000..b5ab9e9 --- /dev/null +++ b/src/test/java/de/eemkeen/MicrostreamApplicationTest.java @@ -0,0 +1,56 @@ +package de.eemkeen; + +import de.eemkeen.model.User; +import de.eemkeen.repo.UserRepository; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.boot.SpringApplication; + +import java.util.Collections; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class MicrostreamApplicationTest { + + @Mock + private UserRepository userRepository; + + @InjectMocks + private MicrostreamApplication microstreamApplication; + + @Test + void listUserOnStartup_shouldAddUserAndPrintUsers() { + // Arrange + User user = User.builder().name("Test User").build(); + when(userRepository.add(any(User.class))).thenReturn(user); + when(userRepository.getAll()).thenReturn(Collections.singletonList(user)); + + // Act + microstreamApplication.listUserOnStartup(); + + // Assert + verify(userRepository).add(any(User.class)); + verify(userRepository).getAll(); + } + + @Test + void main_shouldRunSpringApplication() { + // Use PowerMockito to mock the static method + try (var springAppMock = mockStatic(SpringApplication.class)) { + // Act + String[] args = new String[0]; + MicrostreamApplication.main(args); + + // Assert + springAppMock.verify(() -> + SpringApplication.run(eq(MicrostreamApplication.class), eq(args)) + ); + } + } +} \ No newline at end of file diff --git a/src/test/java/de/eemkeen/MicrostreamApplicationTests.java b/src/test/java/de/eemkeen/MicrostreamApplicationTests.java index 0653c68..d5ebddf 100644 --- a/src/test/java/de/eemkeen/MicrostreamApplicationTests.java +++ b/src/test/java/de/eemkeen/MicrostreamApplicationTests.java @@ -1,11 +1,11 @@ package de.eemkeen; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -@SpringBootTest class MicrostreamApplicationTests { @Test + @Disabled("Skipping integration test as it requires LocalStack to be running") void contextLoads() {} } diff --git a/src/test/java/de/eemkeen/TestConfig.java b/src/test/java/de/eemkeen/TestConfig.java new file mode 100644 index 0000000..5a6f571 --- /dev/null +++ b/src/test/java/de/eemkeen/TestConfig.java @@ -0,0 +1,45 @@ +package de.eemkeen; + +import de.eemkeen.model.Root; +import de.eemkeen.repo.UserRepository; +import one.microstream.afs.blobstore.types.BlobStoreFileSystem; +import one.microstream.storage.embedded.types.EmbeddedStorageManager; +import org.mockito.Mockito; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import software.amazon.awssdk.services.s3.S3Client; + +@TestConfiguration +public class TestConfig { + + @Bean + @Primary + public S3Client s3Client() { + return Mockito.mock(S3Client.class); + } + + @Bean + @Primary + public BlobStoreFileSystem blobStoreFileSystem() { + return Mockito.mock(BlobStoreFileSystem.class); + } + + @Bean + @Primary + public EmbeddedStorageManager embeddedStorageManager() { + return Mockito.mock(EmbeddedStorageManager.class); + } + + @Bean + @Primary + public Root root() { + return Mockito.mock(Root.class); + } + + @Bean + @Primary + public UserRepository userRepository() { + return Mockito.mock(UserRepository.class); + } +} \ No newline at end of file diff --git a/src/test/java/de/eemkeen/configuration/TestConfig.java b/src/test/java/de/eemkeen/configuration/TestConfig.java new file mode 100644 index 0000000..bcc5fe5 --- /dev/null +++ b/src/test/java/de/eemkeen/configuration/TestConfig.java @@ -0,0 +1,34 @@ +package de.eemkeen.configuration; + +import de.eemkeen.model.Root; +import one.microstream.afs.blobstore.types.BlobStoreFileSystem; +import one.microstream.storage.embedded.types.EmbeddedStorageManager; +import org.mockito.Mockito; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import software.amazon.awssdk.services.s3.S3Client; + +@TestConfiguration +public class TestConfig { + + @Bean + @Primary + public S3Client s3Client() { + return Mockito.mock(S3Client.class); + } + + @Bean + @Primary + public BlobStoreFileSystem blobStorage() { + return Mockito.mock(BlobStoreFileSystem.class); + } + + @Bean + @Primary + public EmbeddedStorageManager storageManager() { + EmbeddedStorageManager mock = Mockito.mock(EmbeddedStorageManager.class); + Mockito.when(mock.root()).thenReturn(new Root()); + return mock; + } +} \ No newline at end of file diff --git a/src/test/java/de/eemkeen/model/InitializerTest.java b/src/test/java/de/eemkeen/model/InitializerTest.java new file mode 100644 index 0000000..9ea3d7f --- /dev/null +++ b/src/test/java/de/eemkeen/model/InitializerTest.java @@ -0,0 +1,62 @@ +package de.eemkeen.model; + +import one.microstream.persistence.types.Storer; +import one.microstream.storage.types.StorageManager; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.ArrayList; + +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class InitializerTest { + + @Mock + private StorageManager storageManager; + + @Mock + private Root root; + + @Mock + private Storer storer; + + private Initializer initializer; + + @BeforeEach + void setUp() { + initializer = new Initializer(); + when(storageManager.root()).thenReturn(root); + } + + @Test + void initialize_shouldStoreRootWhenUsersEmpty() { + // Arrange + when(root.getUsers()).thenReturn(new ArrayList<>()); + when(storageManager.createEagerStorer()).thenReturn(storer); + + // Act + initializer.initialize(storageManager); + + // Assert + verify(storageManager).createEagerStorer(); + verify(storer).store(root); + } + + @Test + void initialize_shouldNotStoreRootWhenUsersNotEmpty() { + // Arrange + ArrayList users = new ArrayList<>(); + users.add(User.builder().name("Existing User").build()); + when(root.getUsers()).thenReturn(users); + + // Act + initializer.initialize(storageManager); + + // Assert + verify(storageManager, never()).createEagerStorer(); + } +} \ No newline at end of file diff --git a/src/test/java/de/eemkeen/model/RootTest.java b/src/test/java/de/eemkeen/model/RootTest.java new file mode 100644 index 0000000..7227f39 --- /dev/null +++ b/src/test/java/de/eemkeen/model/RootTest.java @@ -0,0 +1,95 @@ +package de.eemkeen.model; + +import one.microstream.storage.types.StorageManager; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class RootTest { + + @Mock + private StorageManager storageManager; + + private Root root; + private User testUser; + + @BeforeEach + void setUp() { + root = new Root(); + ReflectionTestUtils.setField(root, "storageManager", storageManager); + testUser = User.builder().name("Test User").build(); + } + + @Test + void getUsers_shouldReturnEmptyListInitially() { + // Act + List users = root.getUsers(); + + // Assert + assertNotNull(users); + assertTrue(users.isEmpty()); + } + + @Test + void getUsers_shouldReturnCopyOfUsersList() { + // Arrange + root.addUser(testUser); + + // Act + List users = root.getUsers(); + users.clear(); // Modify the returned list + + // Assert + assertEquals(1, root.getUsers().size()); // Original list should still have the user + } + + @Test + void addUser_shouldAddUserToList() { + // Act + User addedUser = root.addUser(testUser); + + // Assert + assertEquals(testUser, addedUser); + assertEquals(1, root.getUsers().size()); + assertEquals(testUser, root.getUsers().get(0)); + verify(storageManager).store(root.getUsers()); + } + + @Test + void updateUser_shouldStoreUser() { + // Arrange + root.addUser(testUser); + + // Act + root.updateUser(testUser); + + // Assert + verify(storageManager).store(testUser); + } + + @Test + void removeUser_shouldRemoveUserFromList() { + // Arrange + root.addUser(testUser); + assertEquals(1, root.getUsers().size()); + // Reset the mock to clear the first store call from addUser + reset(storageManager); + + // Act + root.removeUser(testUser); + + // Assert + assertTrue(root.getUsers().isEmpty()); + verify(storageManager).store(root.getUsers()); + } +} \ No newline at end of file diff --git a/src/test/java/de/eemkeen/model/UserTest.java b/src/test/java/de/eemkeen/model/UserTest.java new file mode 100644 index 0000000..55f7224 --- /dev/null +++ b/src/test/java/de/eemkeen/model/UserTest.java @@ -0,0 +1,131 @@ +package de.eemkeen.model; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class UserTest { + + @Test + void builder_shouldCreateUserWithProvidedName() { + // Arrange & Act + String name = "Test User"; + User user = User.builder().name(name).build(); + + // Assert + assertEquals(name, user.getName()); + assertNotNull(user.getId()); + } + + @Test + void builder_shouldCreateUserWithProvidedId() { + // Arrange & Act + String id = "custom-id"; + String name = "Test User"; + User user = User.builder().id(id).name(name).build(); + + // Assert + assertEquals(id, user.getId()); + assertEquals(name, user.getName()); + } + + @Test + void builder_shouldGenerateUniqueIds() { + // Arrange & Act + User user1 = User.builder().name("User 1").build(); + User user2 = User.builder().name("User 2").build(); + + // Assert + assertNotEquals(user1.getId(), user2.getId()); + } + + @Test + void equals_shouldReturnTrueForSameInstance() { + // Arrange + User user = User.builder().name("Test User").build(); + + // Act & Assert + assertEquals(user, user); + } + + @Test + void equals_shouldReturnTrueForEqualUsers() { + // Arrange + String id = "test-id"; + String name = "Test User"; + + User user1 = User.builder().id(id).name(name).build(); + User user2 = User.builder().id(id).name(name).build(); + + // Act & Assert + assertEquals(user1, user2); + } + + @Test + void equals_shouldReturnFalseForDifferentIds() { + // Arrange + String name = "Test User"; + + User user1 = User.builder().id("id1").name(name).build(); + User user2 = User.builder().id("id2").name(name).build(); + + // Act & Assert + assertNotEquals(user1, user2); + } + + @Test + void equals_shouldReturnFalseForDifferentTypes() { + // Arrange + User user = User.builder().name("Test User").build(); + Object other = new Object(); + + // Act & Assert + assertNotEquals(user, other); + } + + @Test + void equals_shouldReturnFalseForNull() { + // Arrange + User user = User.builder().name("Test User").build(); + + // Act & Assert + assertNotEquals(user, null); + } + + @Test + void hashCode_shouldBeEqualForEqualUsers() { + // Arrange + String id = "test-id"; + String name = "Test User"; + + User user1 = User.builder().id(id).name(name).build(); + User user2 = User.builder().id(id).name(name).build(); + + // Act & Assert + assertEquals(user1.hashCode(), user2.hashCode()); + } + + @Test + void hashCode_shouldBeDifferentForDifferentUsers() { + // Arrange + User user1 = User.builder().id("id1").name("User 1").build(); + User user2 = User.builder().id("id2").name("User 2").build(); + + // Act & Assert + assertNotEquals(user1.hashCode(), user2.hashCode()); + } + + @Test + void toString_shouldContainUserDetails() { + // Arrange + String name = "Test User"; + User user = User.builder().name(name).build(); + + // Act + String result = user.toString(); + + // Assert + assertTrue(result.contains(name)); + assertTrue(result.contains(user.getId())); + } +} \ No newline at end of file diff --git a/src/test/java/de/eemkeen/repo/UserRepositoryTest.java b/src/test/java/de/eemkeen/repo/UserRepositoryTest.java new file mode 100644 index 0000000..a9c9216 --- /dev/null +++ b/src/test/java/de/eemkeen/repo/UserRepositoryTest.java @@ -0,0 +1,166 @@ +package de.eemkeen.repo; + +import de.eemkeen.exception.UserAlreadyExistsException; +import de.eemkeen.exception.UserNotFoundException; +import de.eemkeen.model.Root; +import de.eemkeen.model.User; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class UserRepositoryTest { + + @Mock + private Root root; + + private UserRepository userRepository; + private User testUser; + private List userList; + + @BeforeEach + void setUp() { + userRepository = new UserRepository(root); + testUser = User.builder().name("Test User").build(); + userList = new ArrayList<>(); + userList.add(testUser); + } + + @Test + void getAll_shouldReturnAllUsers() { + // Arrange + when(root.getUsers()).thenReturn(userList); + + // Act + List result = userRepository.getAll(); + + // Assert + assertEquals(1, result.size()); + assertEquals(testUser, result.get(0)); + verify(root).getUsers(); + } + + @Test + void getById_shouldReturnUserWhenExists() { + // Arrange + String userId = testUser.getId(); + when(root.getUsers()).thenReturn(userList); + + // Act + Optional result = userRepository.getById(userId); + + // Assert + assertTrue(result.isPresent()); + assertEquals(testUser, result.get()); + verify(root).getUsers(); + } + + @Test + void getById_shouldReturnEmptyWhenUserDoesNotExist() { + // Arrange + String nonExistentId = "non-existent-id"; + when(root.getUsers()).thenReturn(userList); + + // Act + Optional result = userRepository.getById(nonExistentId); + + // Assert + assertTrue(result.isEmpty()); + verify(root).getUsers(); + } + + @Test + void add_shouldAddUserSuccessfully() { + // Arrange + User newUser = User.builder().name("New User").build(); + when(root.getUsers()).thenReturn(userList); + when(root.addUser(any(User.class))).thenReturn(newUser); + + // Act + User result = userRepository.add(newUser); + + // Assert + assertEquals(newUser, result); + verify(root).getUsers(); + verify(root).addUser(any(User.class)); + } + + @Test + void add_shouldThrowExceptionWhenUserAlreadyExists() { + // Arrange + when(root.getUsers()).thenReturn(userList); + + // Act & Assert + assertThrows(UserAlreadyExistsException.class, () -> userRepository.add(testUser)); + verify(root).getUsers(); + verify(root, never()).addUser(any(User.class)); + } + + @Test + void updateName_shouldUpdateUserNameSuccessfully() { + // Arrange + String userId = testUser.getId(); + String newName = "Updated Name"; + when(root.getUsers()).thenReturn(userList); + + // Act + User result = userRepository.updateName(userId, newName); + + // Assert + assertEquals(userId, result.getId()); + assertEquals(newName, result.getName()); + verify(root).getUsers(); + verify(root).updateUser(any(User.class)); + } + + @Test + void updateName_shouldThrowExceptionWhenUserDoesNotExist() { + // Arrange + String nonExistentId = "non-existent-id"; + String newName = "Updated Name"; + when(root.getUsers()).thenReturn(userList); + + // Act & Assert + assertThrows(UserNotFoundException.class, () -> userRepository.updateName(nonExistentId, newName)); + verify(root).getUsers(); + verify(root, never()).updateUser(any(User.class)); + } + + @Test + void removeById_shouldRemoveUserWhenExists() { + // Arrange + String userId = testUser.getId(); + when(root.getUsers()).thenReturn(userList); + + // Act + userRepository.removeById(userId); + + // Assert + verify(root).getUsers(); + verify(root).removeUser(testUser); + } + + @Test + void removeById_shouldDoNothingWhenUserDoesNotExist() { + // Arrange + String nonExistentId = "non-existent-id"; + when(root.getUsers()).thenReturn(userList); + + // Act + userRepository.removeById(nonExistentId); + + // Assert + verify(root).getUsers(); + verify(root, never()).removeUser(any(User.class)); + } +} \ No newline at end of file diff --git a/src/test/resources/application-test.properties b/src/test/resources/application-test.properties new file mode 100644 index 0000000..cc3a79b --- /dev/null +++ b/src/test/resources/application-test.properties @@ -0,0 +1,7 @@ +# Test configuration +spring.main.allow-bean-definition-overriding=true +spring.docker.compose.skip.in-tests=true + +# Disable S3 integration for tests +microstream.storage.type=embedded-filesystem +microstream.storage.location=target/microstream-test-storage \ No newline at end of file