I've created a helper method to fetch "User" object from the authenticated user as below.
@Component
@RequiredArgsConstructor
public class AuthUserUtil {
@NonNull
private final UserService userService;
@NonNull
private final DTOConversionUtil dtoConversionUtil;
private User user;
public User getAuthUser() {
if (this.user != null) {
return this.user;
}
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
if (authentication.isAuthenticated() && !StringUtils.isBlank(authentication.getName())) {
this.user = dtoConversionUtil.convertToEntity(
userService.getUserByUserName(
authentication.getName()
)
);
return this.user;
} else throw new UsernameNotFoundException("Invalid user or not authorized");
} else {
throw new UsernameNotFoundException("Invalid user or not authorized");
}
}
}
And I'm using it inside a service class to fetch the authenticated user whenever necessary.
@Service
@Validated
public class CategoryServiceImpl implements CategoryService {
private final CategoryRepository categoryRepository;
private final DTOConversionUtil dtoConversionUtils;
private final AuthUserUtil authUserUtil;
public CategoryServiceImpl(CategoryRepository categoryRepository, DTOConversionUtil dtoConversionUtils, AuthUserUtil authUserUtil) {
this.categoryRepository = categoryRepository;
this.dtoConversionUtils = dtoConversionUtils;
this.authUserUtil = authUserUtil;
}
@Override
public List<CategoryDTO> getAllCategories() throws Exception {
List<Category> categories = categoryRepository.findAllByUser(authUserUtil.getAuthUser());
if (categories.isEmpty()) {
throw new Exception("No Categories found");
}
List<CategoryDTO> resultList = new ArrayList<>();
categories.forEach(category -> resultList.add(dtoConversionUtils.convertToDTO(category)));
return resultList;
}
@Override
public CategoryDTO getCategory(int categoryID) throws CategoryNotFoundException {
Optional<Category> category = categoryRepository.findByIdAndUser(categoryID, authUserUtil.getAuthUser());
if (category.isEmpty()) {
throw new CategoryNotFoundException("Category with ID: " + categoryID + " not found!");
}
return dtoConversionUtils.convertToDTO(category.get());
}
@Override
public CategoryDTO getCategory(String categoryName) throws CategoryNotFoundException {
if (StringUtils.isBlank(categoryName)) {
throw new CategoryNotFoundException("Empty Category Name given");
}
Optional<Category> category = categoryRepository.findByCategoryNameAndUser(categoryName, authUserUtil.getAuthUser());
if (category.isEmpty()) {
throw new CategoryNotFoundException("No category found with the given name \"" + categoryName + "\"");
}
return dtoConversionUtils.convertToDTO(category.get());
}
@Override
public CategoryDTO saveCategory(@Valid CategoryDTO newCategory) throws CategoryAlreadyExistsException {
// Check if a category exists with the given category name.
if (categoryRepository.findByCategoryNameAndUser(newCategory.getCategoryName(), authUserUtil.getAuthUser()).isEmpty()) {
Category categoryToBeSaved = dtoConversionUtils.convertToEntity(newCategory);
categoryToBeSaved.setUser(authUserUtil.getAuthUser());
return dtoConversionUtils
.convertToDTO(categoryRepository.save(categoryToBeSaved));
} else {
throw new CategoryAlreadyExistsException("Category already exists with the given name");
}
}
@Override
public Integer deleteCategory(int categoryID) throws CategoryNotFoundException {
Optional<Category> category = categoryRepository.findByIdAndUser(categoryID, authUserUtil.getAuthUser());
if (category.isEmpty()) {
throw new CategoryNotFoundException("No category found with the given ID");
}
categoryRepository.delete(category.get());
return category.get().getId();
}
@Override
public CategoryDTO updateCategory(int categoryID, @Valid @NonNull UpdateCategoryDTO updatedCategoryDTO)
throws CategoryNotFoundException, CategoryAlreadyExistsException {
Optional<Category> categoryOptional = categoryRepository.findByIdAndUser(categoryID, authUserUtil.getAuthUser());
if (categoryOptional.isEmpty()) {
throw new CategoryNotFoundException("No category found with the given ID");
}
Category category = categoryOptional.get();
if (!category.getCategoryName().equals(updatedCategoryDTO.getCategoryName())) {
Optional<Category> checkCategory = categoryRepository
.findByCategoryNameAndUser(updatedCategoryDTO.getCategoryName(), authUserUtil.getAuthUser());
if (checkCategory.isPresent() && !checkCategory.get().getId().equals(category.getId())) {
throw new CategoryAlreadyExistsException("Duplicate category exists with the given name");
}
}
Category updatedCategory;
if (category.getId() == categoryID && updatedCategoryDTO.getID().equals(category.getId())) {
category.setCategoryName(updatedCategoryDTO.getCategoryName());
category.setIconClassName(updatedCategoryDTO.getIconClassName());
category.setUser(authUserUtil.getAuthUser());
updatedCategory = categoryRepository.save(category);
return dtoConversionUtils.convertToDTO(updatedCategory);
} else {
throw new CategoryNotFoundException("Please enter valid category details");
}
}
@Override
public List<CategoryDTO> searchCategory(String searchQueryString) throws InvalidValueException, CategoryNotFoundException {
if (StringUtils.isBlank(searchQueryString))
throw new InvalidValueException("Empty search query given.");
List<Category> searchedCategories = categoryRepository.findByCategoryNameContainingAndUser(searchQueryString, authUserUtil.getAuthUser());
if (searchedCategories.isEmpty()) throw new CategoryNotFoundException("No Categories found with the query: " + searchQueryString);
return searchedCategories.stream().map(dtoConversionUtils::convertToDTO).collect(Collectors.toList());
}
}
And created test for the above class as follows:
@SuppressWarnings("ConstantConditions")
@SpringBootTest
@ActiveProfiles("test")
class CategoryServiceImplTest {
@Autowired
private CategoryServiceImpl categoryService;
@MockBean
private UserService userService;
@MockBean
private CategoryRepository categoryRepository;
private final LocalDateTime dateTime = LocalDateTime.now();
private final Role userRole = new Role(1, "ROLE_USER", dateTime, dateTime);
private final Set<Role> rolesList = new HashSet<>();
private final UserDTO userDTO = new UserDTO(1, "user1", "First Name", "Last Name", "e@m.com", 10d, 10d);
private User user;
@BeforeEach
void setUp() {
rolesList.add(userRole);
user = new User(1,
"user1",
"password",
"First Name",
"Last Name",
"e@m.com",
Money.of(CurrencyUnit.of("INR"), 10),
Money.of(CurrencyUnit.of("INR"), 10),
true,
false,
false,
false,
rolesList,
dateTime,
dateTime);
when(userService.getUserByUserName(Mockito.anyString())).thenReturn(userDTO);
}
@Test
@WithMockUser
void givenCategoryDTOToSaveShouldReturnCategory() throws CategoryAlreadyExistsException {
CategoryDTO categoryDTO = new CategoryDTO("Category 1", "Icon 1");
when(categoryRepository.save(Mockito.any(Category.class)))
.thenReturn(new Category(1, "Category 1", "Icon 1", null, user, dateTime, dateTime));
when(userService.getUserByUserName(Mockito.anyString())).thenReturn(userDTO);
CategoryDTO resultDto = categoryService.saveCategory(categoryDTO);
CategoryDTO expectedDto = new CategoryDTO(1, "Category 1", "Icon 1");
assertNotNull(resultDto);
assertEquals(expectedDto, resultDto);
}
@Test
@WithMockUser
void givenCategoryWithInvalidUserShouldThrowError() {
CategoryDTO categoryDTO = new CategoryDTO("Category 1", "Icon 1");
when(userService.getUserByUserName(Mockito.anyString())).thenThrow(UsernameNotFoundException.class);
assertThrows(UsernameNotFoundException.class, () -> categoryService.saveCategory(categoryDTO));
}
@Test
void givenCategoryAndNullAsUserToSaveShouldThrowError() {
CategoryDTO categoryDTO = new CategoryDTO("Category 1", "Icon 1");
assertThrows(UsernameNotFoundException.class, () -> categoryService.saveCategory(categoryDTO));
}
@Test
@WithMockUser
void givenDuplicateCategoryToSaveShouldThrowError() {
Category category = new Category(1, "Category 1", "Icon 1", null, user, dateTime, dateTime);
when(categoryRepository.findByCategoryNameAndUser(Mockito.anyString(), Mockito.any(User.class))).thenReturn(Optional.of(category));
CategoryAlreadyExistsException ex = assertThrows(CategoryAlreadyExistsException.class, () -> {
CategoryDTO categoryDTO = new CategoryDTO("Category 1", "Icon 1");
categoryService.saveCategory(categoryDTO);
});
assertEquals("Category already exists with the given name", ex.getMessage());
}
@Test
void givenCategoryWithInvalidValuesToSaveShouldThrowError() {
ConstraintViolationException exception = assertThrows(ConstraintViolationException.class, () -> {
CategoryDTO categoryDTO = new CategoryDTO(null, null, "");
categoryService.saveCategory(categoryDTO);
});
exception.getConstraintViolations().forEach(violation -> assertEquals("must not be blank", violation.getMessage()));
}
@Test
@WithMockUser
void givenCallToGetAllCategoriesShouldGiveCategoriesList() throws Exception {
List<CategoryDTO> expectedCategoryDTOs = new ArrayList<>();
expectedCategoryDTOs.add(new CategoryDTO(1, "Category 1", "Icon 1"));
List<Category> categories = new ArrayList<>();
categories.add(new Category(1, "Category 1", "Icon 1", null, user, dateTime, dateTime));
when(categoryRepository.findAllByUser(Mockito.any(User.class))).thenReturn(categories);
List<CategoryDTO> resultCategoryDTOs = categoryService.getAllCategories();
assertEquals(expectedCategoryDTOs, resultCategoryDTOs);
}
@Test
@WithMockUser
void givenCallToGetAllCategoriesShouldReturnError() {
when(categoryRepository.findAll()).thenReturn(new ArrayList<>());
Exception ex = assertThrows(Exception.class, () -> categoryService.getAllCategories());
assertEquals("No Categories found", ex.getMessage());
}
@Test
@WithMockUser
void givenIDToGetCategoryShouldReturnCategory() throws CategoryNotFoundException {
when(categoryRepository.findByIdAndUser(Mockito.anyInt(), Mockito.any(User.class)))
.thenReturn(Optional.of(new Category(1, "Category 1", "Icon 1", null, user, dateTime, dateTime)));
CategoryDTO expectedDto = new CategoryDTO(1, "Category 1", "Icon 1");
CategoryDTO resultDto = categoryService.getCategory(1);
assertNotNull(resultDto);
assertEquals(expectedDto, resultDto);
}
@Test
@WithMockUser
void givenIDToGetCategoryShouldThrowError() {
when(categoryRepository.findById(Mockito.anyInt())).thenReturn(Optional.empty());
CategoryNotFoundException ex = assertThrows(CategoryNotFoundException.class, () -> categoryService.getCategory(1));
assertEquals("Category with ID: 1 not found!", ex.getMessage());
}
@Test
void givenNullToGetCategoryShouldThrowError() {
assertThrows(NullPointerException.class, () -> categoryService.getCategory((Integer) null));
}
@Test
@WithMockUser
void givenCategoryNameToGetCategoryShouldReturnCategory() throws CategoryNotFoundException {
when(categoryRepository.findByCategoryNameAndUser(Mockito.anyString(), Mockito.any(User.class)))
.thenReturn(Optional.of(new Category(1, "Category 1", "Icon 1", null, user, dateTime, dateTime)));
CategoryDTO expectedDto = new CategoryDTO(1, "Category 1", "Icon 1");
CategoryDTO resultDto = categoryService.getCategory("Category 1");
assertNotNull(resultDto);
assertEquals(expectedDto, resultDto);
}
@Test
@WithMockUser
void givenCategoryNameToGetCategoryShouldThrowError() {
when(categoryRepository.findByCategoryName(Mockito.anyString())).thenReturn(Optional.empty());
CategoryNotFoundException ex = assertThrows(CategoryNotFoundException.class, () -> categoryService.getCategory("Category 1"));
assertEquals("No category found with the given name \"Category 1\"", ex.getMessage());
}
@Test
void givenNullAsCategoryNameToGetCategoryShouldThrowError() {
assertThrows(CategoryNotFoundException.class, () -> categoryService.getCategory(null));
}
@Test
void givenBlankAsCategoryNameToGetCategoryShouldThrowError() {
CategoryNotFoundException ex = assertThrows(CategoryNotFoundException.class, () -> categoryService.getCategory(""));
assertEquals("Empty Category Name given", ex.getMessage());
}
@Test
@WithMockUser
void givenIDToDeleteShouldGiveInteger() throws CategoryNotFoundException {
when(categoryRepository.findByIdAndUser(Mockito.anyInt(), Mockito.any(User.class)))
.thenReturn(Optional.of(new Category(1, "Category 1", "Icon 1", null, user, dateTime, dateTime)));
Integer resultID = categoryService.deleteCategory(1);
assertEquals(1, resultID);
}
@Test
@WithMockUser
void givenIDToDeleteShouldThrowError() {
when(categoryRepository.findById(Mockito.anyInt())).thenReturn(Optional.empty());
CategoryNotFoundException ex = assertThrows(CategoryNotFoundException.class, () -> categoryService.deleteCategory(1));
assertEquals("No category found with the given ID", ex.getMessage());
}
@Test
void givenNullAsIDToDeleteShouldThrowError() {
assertThrows(NullPointerException.class, () -> categoryService.deleteCategory((Integer) null));
}
@Test
@WithMockUser
void givenIDAndCategoryToUpdateShouldReturnUpdatedCategory() throws CategoryNotFoundException, CategoryAlreadyExistsException {
Category category = new Category(1, "Category 1", "Icon 1", null, user, dateTime, dateTime);
CategoryDTO expectedCategoryDTO = new CategoryDTO(1, "Updated Category", "Icon 1");
when(categoryRepository.findByIdAndUser(Mockito.anyInt(), Mockito.any(User.class))).thenReturn(Optional.of(category));
when(categoryRepository.findByCategoryNameAndUser(Mockito.anyString(), Mockito.any(User.class))).thenReturn(Optional.empty());
when(categoryRepository.save(Mockito.any(Category.class)))
.thenReturn(new Category(1, "Updated Category", "Icon 1", null, user, dateTime, dateTime));
CategoryDTO resultCategoryDTO = categoryService.updateCategory(1,
new UpdateCategoryDTO(1, "Updated Category", "Icon 1"));
assertNotNull(resultCategoryDTO);
assertEquals(expectedCategoryDTO, resultCategoryDTO);
}
@Test
@WithMockUser
void givenNonExistentIDAndCategoryToUpdateShouldThrowError() {
when(categoryRepository.findByIdAndUser(Mockito.anyInt(), Mockito.any(User.class))).thenReturn(Optional.empty());
CategoryNotFoundException ex = assertThrows(CategoryNotFoundException.class, () -> categoryService.updateCategory(1, new UpdateCategoryDTO(1, "Updated Category", "Icon 1")));
assertEquals("No category found with the given ID", ex.getMessage());
}
@Test
@WithMockUser
void givenIDAndDuplicateCategoryToUpdateShouldThrowError() {
Category category = new Category(1, "Category 1", "Icon 1", null, user, dateTime, dateTime);
when(categoryRepository.findByIdAndUser(Mockito.anyInt(), Mockito.any(User.class))).thenReturn(Optional.of(category));
when(categoryRepository.findByCategoryNameAndUser(Mockito.anyString(), Mockito.any(User.class)))
.thenReturn(Optional.of(new Category(2, "Category 1", "Icon 1", null, user, dateTime, dateTime)));
CategoryAlreadyExistsException ex = assertThrows(CategoryAlreadyExistsException.class, () -> categoryService.updateCategory(1, new UpdateCategoryDTO(2, "Updated Category", "Icon 1")));
assertEquals("Duplicate category exists with the given name", ex.getMessage());
}
@Test
@WithMockUser
void givenMissMatchedIDAndCategoryToUpdateShouldThrowError() {
Category category = new Category(1, "Category 1", "Icon 1", null, user, dateTime, dateTime);
when(categoryRepository.findByIdAndUser(Mockito.anyInt(), Mockito.any(User.class))).thenReturn(Optional.of(category));
when(categoryRepository.findByCategoryNameAndUser(Mockito.anyString(), Mockito.any(User.class))).thenReturn(Optional.empty());
CategoryNotFoundException ex = assertThrows(CategoryNotFoundException.class, () -> categoryService.updateCategory(1, new UpdateCategoryDTO(2, "Updated Category", "Icon 1")));
assertEquals("Please enter valid category details", ex.getMessage());
}
@Test
void givenNullIDToUpdateShouldThrowError() {
assertThrows(NullPointerException.class,
() -> categoryService.updateCategory((Integer) null, new UpdateCategoryDTO()));
}
@Test
void givenIDAndNullAsUpdatedCategoryToUpdateShouldThrowError() {
assertThrows(NullPointerException.class, () -> categoryService.updateCategory(1, null));
}
@Test
void givenIDAndCategoryWithNullValuesToUpdateShouldThrowError() {
ConstraintViolationException ex = assertThrows(ConstraintViolationException.class,
() -> categoryService.updateCategory(1, new UpdateCategoryDTO(null, null, null)));
assertEquals(ex.getConstraintViolations().size(), 3);
// Test with Null ID
ex = assertThrows(ConstraintViolationException.class,
() -> categoryService.updateCategory(1, new UpdateCategoryDTO(null, "Update", "Icon")));
assertEquals(ex.getConstraintViolations().size(), 1);
ex.getConstraintViolations().forEach(violation -> assertEquals("must not be null", violation.getMessage()));
// Test with Blank Category Name and Icon name
ex = assertThrows(ConstraintViolationException.class,
() -> categoryService.updateCategory(1, new UpdateCategoryDTO(1, "", " ")));
assertEquals(ex.getConstraintViolations().size(), 2);
ex.getConstraintViolations().forEach(violation -> assertEquals("must not be blank", violation.getMessage()));
}
@Test
@WithMockUser
void givenQueryStringToSearchShouldReturnCategoriesList() throws InvalidValueException, CategoryNotFoundException {
List<CategoryDTO> expectedCategoryDTOs = new ArrayList<>();
expectedCategoryDTOs.add(new CategoryDTO(1, "Category 1", "Icon 1"));
List<Category> categories = new ArrayList<>();
categories.add(new Category(1, "Category 1", "Icon 1", null, user, dateTime, dateTime));
when(categoryRepository.findByCategoryNameContainingAndUser(Mockito.anyString(), Mockito.any(User.class))).thenReturn(categories);
List<CategoryDTO> result = categoryService.searchCategory("a");
assertFalse(result.isEmpty());
assertEquals(expectedCategoryDTOs, result);
}
@Test
@WithMockUser
void givenInvalidQueryStringToSearchShouldThrowError() {
InvalidValueException exception = assertThrows(InvalidValueException.class, () -> categoryService.searchCategory(""));
assertEquals("Empty search query given.", exception.getMessage());
exception = assertThrows(InvalidValueException.class, () -> categoryService.searchCategory(" "));
assertEquals("Empty search query given.", exception.getMessage());
exception = assertThrows(InvalidValueException.class, () -> categoryService.searchCategory(null));
assertEquals("Empty search query given.", exception.getMessage());
}
@Test
@WithMockUser
void givenQueryStringToSearchShouldThrowError() {
when(categoryRepository
.findByCategoryNameContainingAndUser(Mockito.anyString(),
Mockito.any(User.class))
).thenReturn(new ArrayList<>());
CategoryNotFoundException categoryNotFoundException = assertThrows(CategoryNotFoundException.class,
() -> categoryService.searchCategory("a")
);
assertEquals("No Categories found with the query: a", categoryNotFoundException.getMessage());
}
}
I've written test cases for both authorized and unauthorized users. But when I run the test class, the tests for unauthorized users fail.
For test methods givenCategoryWithInvalidUserShouldThrowError() and givenCategoryAndNullAsUserToSaveShouldThrowError() I get the below error message.
org.opentest4j.AssertionFailedError: Unexpected exception type thrown ==> expected: <org.springframework.security.core.userdetails.UsernameNotFoundException> but was: <java.lang.IllegalArgumentException>
at org.junit.jupiter.api.AssertThrows.assertThrows(AssertThrows.java:65)
at org.junit.jupiter.api.AssertThrows.assertThrows(AssertThrows.java:37)
at org.junit.jupiter.api.Assertions.assertThrows(Assertions.java:2952)
at com.bsd.dailyexpensetracker.service.CategoryServiceImplTest.givenCategoryWithInvalidUserShouldThrowError(CategoryServiceImplTest.java:109)
But when I run the failed test methods(givenCategoryWithInvalidUserShouldThrowError() and givenCategoryAndNullAsUserToSaveShouldThrowError()) induvidually, they are getting passed.
So I tried to debug the test class and found out the SecurityContextHolder is not getting cleared or reset after every test method eventhough I use @WithMockUser for only some test methods. And because of this the helper method in AuthUserUtils was not throwing any expected UsernameNotFoundExceptions and continued to execute which lead to unexpected exceptions. I placed the @WithMockUser annotation on a single test method and removed it from other test methods and executed the test class, the SecurityContext was still getting applied to all the test methods.
This occurs only when I run the whole test class. When I run induvidual test cases, they are executing as expected i.e the SecurityContextHolder was getting set only when required.
When I put the helper method in the Service class with all the required dependencies, and run the test class, all the tests are getting passed.
So is there something mising in my Util class or did I configure my test class wrong?
Aucun commentaire:
Enregistrer un commentaire