How to test Service Layer with JPA Repos - java

I have s service methos which i want to test:
#Override
public void updateImage(long id, ImageAsStream imageAsStream) {
Product product = productRepository.findById(id)
.orElseThrow(() -> new ProductException("Product can not be found"));
updateProductImage(imageAsStream, product.getImage().getId());
}
private void updateProductImage(ImageAsStream imageAsStream, Long existingImageId) {
imageRepository.updateProductImage(existingImageId, imageAsStream);
imageRepository.copyImageToThumbnail(existingImageId);
}
So to be able to call service method, i need to mock imageRepository somehow:
#Test
void updateProductImage() {
when(imageRepository)
.updateProductImage(1L, imageAsStream).thenReturn(???);
productService.updateProductImage(1L, imageAsStream);
}
Can you please advise whats the general approach in such cases?

When I would need to test this method, then these things need to be validated:
The id is of an existing product and there is a call to the imageRepository to update the product image
The id is not of an existing product. An exception is thrown and nothing is saved in the imageRepository
For your question, it does not really matter what you return there. It can be a mock of Product, or it can be a real instance.
My preference is usually to have an Object Mother, for example ProductMother to create a "default" instance.
In code:
class ProductServiceTest {
#Test
void testHappyFlow() {
ProductRepository repository = mock(ProductRepository.class);
ProductService service = new ProductService(repository);
when(repository.findById(1L))
.thenReturn(ProductMother.createDefaultProduct());
ImageAsStream imageAsStream = mock(ImageAsStream.class);
service.updateImage(1L, imageAsStream);
verify(repository).updateProductImage(1L, imageAsStream);
verify(repository).copyImageToThumbnail(1L);
}
#Test
void testProductNotFound() {
ProductRepository repository = mock(ProductRepository.class);
ProductService service = new ProductService(repository);
assertThatExceptionOfType(ProductException.class)
.isThrownBy( () -> {
ImageAsStream imageAsStream = mock(ImageAsStream.class);
service.updateImage(1L, imageAsStream);
});
}
}

Related

How to create mock test to soft-delete void method?

I would like to test my delete method which looks like:
public void deleteUser(String id) {
var userEntity = userRepository.findById(Integer.valueOf(id))
.orElseThrow(() -> new UserNotFoundException("Id not found"));
if (userEntity.getLastAccessDate() == null) {
throw new ProhibitedAccessException("Policy has been violated");
}
userRepository.delete(userEntity);
}
My delete method in repository is the following:
#Modifying
#Query("update UserEntity u set deleted = true where u = :userEntity")
void delete(UserEntity userEntity);
And I've written the following test:
#Test
void deleteUserTest(){
final int id = 1;
UserEntity userEntity = new UserEntity();
var idString = String.valueOf(id);
when(userRepository.findById(id)).thenReturn(Optional.of(userEntity));
assertThrows(RuntimeException.class, () -> userService.deleteUser(idString));
}
This test is working good but it didn't cover the
userRepository.delete(userEntity);
Could you help me please - how can I add it to my test? Previously, I've tried to to do it through verify but it didn't help.
Test coverage means, which lines of your code are being called. If you mock an object, you are not calling the real code but only simulate the behaviour
Your only test the implementation of your userService and mock the behaviour of your userRepository.
So your test only covers the code inside of your userService.deleteUser(...) method, but not the code inside of your userRepository.
If you want to cover your userRepository, you have to write a test with a 'real' userRepository.

check if entity exist and then delete it in one transaction in spring app

i have a services layer and a repository layer in my spring boot application (i use also spring data, mvc etc)
before deleting an entity from the database, I want to check if such an entity exists and if not, then throw an EntityNotFoundException
for example my repository:
public interface RoomRepository extends CrudRepository<Room, Long> {
#Query("from Room r left join fetch r.messages where r.id = :rId")
Optional<Room> findByIdWithMessages(#Param("rId") long id);
#Override
List<Room> findAll();
}
and service:
#Service
#Loggable
public class RoomService implements GenericService<Room> {
private final RoomRepository roomRepository;
private final RoomDtoMapper roomMapper;
public RoomService(RoomRepository roomRepository, RoomDtoMapper roomMapper) {
this.roomRepository = roomRepository;
this.roomMapper = roomMapper;
}
#Override
public Room getById(long id) {
return roomRepository.findById(id).orElseThrow(
() -> new EntityNotFoundException(String.format("room with id = %d wasn't found", id)));
}
#Override
public void delete(Room room) {
getById(room.getId());
roomRepository.delete(room);
}
}
In this example in the delete method, I call the
getById(room.getId())
(so that it throws an EntityNotFoundException if the entity does not exist.)
before
roomRepository.delete(room);
it seems to me that such code is not thread-safe and the operation is not atomic
(because at the moment when in this thread at the moment of checking another request from another thread may already delete the same entity)
and I don't know if I'm doing the right thing
maybe i should add the #Transactional annotation?
would it allow me to make the method atomic?
like this:
#Override
#Transactional
public void delete(Room room) {
getById(room.getId());
roomRepository.delete(room);
}
maybe i should set some kind of isolation level?
you can test if your object needed, exist or not by autowiring the repository injected (in your case is RoomRepository e.g) and (insted User in my exmaple you can use Room): for example:
public ResponseEntity<Object> deletUserById(Long id) {
if (userrRepository.findById(id).isPresent()) {
userrRepository.deleteById(id);
return ResponseEntity.ok().body("User deleted with success");
} else {
return ResponseEntity.unprocessableEntity().body("user to be deleted not exist");
}
}

DB test entries not showing when called via api

I am trying to test my API in a Quarkus APP.
My test setup is I define an object and persist it to the DB with a direct call to the service object. I then call the API and expect to get the object back, but I don't....
My test class setup is;
#QuarkusTest
#TestTransaction
#TestHTTPEndpoint(CompanyController.class)
public class CompanyControllerIntegrationTest {
#Inject
CompanyService service;
#ClassRule
private static PostgreSQLContainer<?> db = new PostgreSQLContainer<>("postgres:13.3-alpine")
.withDatabaseName("db")
.withUsername("user")
.withPassword("password");
#Test
void test_getCompanyThatExist() {
service.createCompany(createCompany());
given()
.when().get("/1")
.then().statusCode(200)
.body("size()", is(1));
}
private Company createCompany() {
Company company = new Company();
return company;
}
}
Controller endpoint is;
#GET
#Path("/{id}")
public Response getCompany(#PathParam("id") Long id) {
System.out.println("[CompanyController] Getting company with id - " + id);
Company company = service.getCompany(id);
System.out.println("[CompanyController] Company got was " + company);
return Response
.ok()
.entity(company)
.build();
Service call is;
public Company getCompany(Long id) {
Company company = repository.findById(id);
System.out.println("[CompanyService] Got company - " + company);
return company;
}
And the print outs, which really confuses me....
So the object is persisted with an ID of 1, but when I go to get the object with the ID of 1 its null. Any ideas why? As I am completely stumped at this stage.
QuarkusTest annotation uses JUnit Jupiter's ExtendWith annotation so in this case you should use #Container instead of #ClassRule, add #Testcontainer at class level and add org.testcontainers:junit-jupiter to your pom.xml or gradle.build
Your test should looks like
#Testcontainers
#QuarkusTest
#TestTransaction
#TestHTTPEndpoint(CompanyController.class)
public class CompanyControllerIntegrationTest {
#Inject
CompanyService service;
#Container
private static PostgreSQLContainer<?> db = new PostgreSQLContainer<>("postgres:13.3-alpine")
.withDatabaseName("db")
.withUsername("user")
.withPassword("password");
}
The container can be started/stopped manually too
#QuarkusTest
#TestTransaction
#TestHTTPEndpoint(CompanyController.class)
public class CompanyControllerIntegrationTest {
#Inject
CompanyService service;
private static PostgreSQLContainer<?> db = new PostgreSQLContainer<>("postgres:13.3-alpine")
.withDatabaseName("db")
.withUsername("user")
.withPassword("password");
#BeforeAll
void beforeAll() {
db.start();
}
#AfterAll
void beforeAll() {
db.stop();
}
}

mocking a method inside another method return null

I have service method "customerDetails" to get more info about customer and change some field that using another method inside it "getById" when I do the unit test for customerDetails I mock the other method but the test faild because the mock return null . I try some solutions I found like checking the order of dependencies and using #InjectMocks (which I do)but they did not work for me and I do not know where the problem is.
code snippet to understand me better
customerService
public class customerService {
public Customer customerDetails(int id) {
CustomerDto customer = getById(id) //here is the problem
// rest of the code
}
public CustomerDto getById(int id) {
Optional<Customer> customer =
this.customerRepository.findCustomerByIdAndIsDeletedFalse(id); //return null here
if (!customer.isPresent()) {
// code to throw Exception customer not found
}
//code to retrieve customer
}
}
customerServiceTest
public class CustomerServiceTest {
#Mock
private CustomerRepository customerRepository;
#InjectMocks
private CustomerService customerService;
#BeforeEach
public void createMocks() {
MockitoAnnotations.initMocks(this);
}
#Test
public void testCustomerDetails() {
CustomerDto actualResponse = DummyCutomer.createDto(); // the actualResponse is filled successfully
when(customerService.getById(actualResponse.getId()).thenReturn(actualResponse); // but here it send to getById null !!
//rest of code
}
}
You need to mock the customerRepository not the getById method
For example:
when(customerRepository.findCustomerByIdAndIsDeletedFalse(actualResponse.getId()).thenReturn(Optional.of(WHAT_EVER_YOU_WANT));
In your case I think you have tow scenarios:
findCustomerByIdAndIsDeletedFalse => Optional.of(a customer instance)
findCustomerByIdAndIsDeletedFalse => Optional.empty()

How can I write unit tests for private methods of service and for public methods with private inside?

I have service:
#Slf4j
#Service
public class CashierServiceDefault implements CashierService {
private final UserRepository userRepository;
#Autowired
public CashierServiceDefault(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
#Transactional
public CashierDto login(CashierDto cashier) {
User dbUser = userRepository.findOneByLoginAndPassword(cashier.getLogin(), cashier.getPassword());
validateCashier(cashier.getLogin(), dbUser);
User userWithToken = createAuthToken(dbUser);
return domainUserToCashierDto(userWithToken, cashier);
}
private void validateCashier(String login, User dbUser) {
if (dbUser == null) {
log.error("Cashier: {} not found", login);
throw new AuthException(AuthException.ErrorCode.USER_NOT_FOUND_EXCEPTION);
}
UserRole userRole = UserRole.valueOf(dbUser.getUserRole().getCode());
if (userRole != UserRole.CASHIER) {
log.error("User: {} has role: {}. expected: CASHIER ", login, userRole.toString());
throw new AuthException(AuthException.ErrorCode.USER_ROLE_NOT_PERMISSION_EXCEPTION);
}
}
private User createAuthToken(User user) {
user.setAuthToken(TokenGenerator.nextToken());
user.setAuthTokenCreatedDate(new Date());
return userRepository.save(user);
}
private CashierDto domainUserToCashierDto(User user, CashierDto cashier) {
//mapping user's fields to CashierDto,s fields
return cashier;
}
I want create Test for this service. I tried this:
#RunWith(SpringRunner.class)
public class CashierServiceDefaultTest {
#MockBean
private UserRepository userRepository;
private CashierService cashierService;
#Before
public void setUp() throws Exception {
cashierService = new CashierServiceDefault(userRepository);
}
#Test
public void login() {
CashierDto cashierDto = new CashierDto();
cashierDto.setLogin("Alex");
cashierDto.setPassword("123");
User user = new User();
user.setLogin("Alex");
user.setPassword("123");
//and other test values
when(userRepository.findOneByLoginAndPassword(cashierDto.getLogin(), cashierDto.getPassword())).thenReturn(user);
CashierDto found = cashierService.login(cashierDto);
assertThat(found.getAuthToken()).isEqualTo("123");
}
And I have questions:
1. How can I tests private methods in my service? Do I need to test them? If so, how?
2. How should I test the public login method? I made a stub for repository methods:
when(userRepository.findOneByLoginAndPassword(cashierDto.getLogin(), cashierDto.getPassword())).thenReturn(user);
But should I do stubs for internal service methods?(validateCashier, createAuthToken, domainUserToCashierDto). If so, how?
UnitTests do not test code, they verify public observable behavior which is return values and communication with dependencies.
private methods are implementation details which you test indirectly (as stated by JWo)
The reason is that you later may change your implementation details (refactor them) whithout breaking any of your existing UnitTests.
I would not test them directly. Since you implemented them as a help for some other methods, you can test those. By testing all public methods you will test the private ones, too. Don't forget to add inputs and outputs for the private methods, when testing the public ones.
Another way is to put test methods into the same package as the production code. Then you have to set your private methods to package or protected.

Categories