JUnit test to delete an entity by id fails - java

I am trying to make a test for the method to delete an entity by id. I've written the test for the case when an entity with the given id doesn't exist, but in case when the entity exists, I'm apparently doing something wrong. This is the delete method from the service. I'm using the deleteById() method of the JpaRepository.
public void deleteById(Integer id) {
Optional<Agency> optionalAgency = repo.findAll().stream()
.filter(agency -> agency.getId().equals(id))
.findFirst();
if (optionalAgency.isPresent()) {
repo.deleteById(optionalAgency.get().getId());
} else {
throw new AgencyNotFoundException("Agency not found");
}
}
And this is my test:
#Mock
private AgencyRepository agencyRepository;
#Mock
private AgencyMapper mapper;
#InjectMocks
private AgencyService agencyService;
#Test
void delete() {
Agency agency = new Agency();
when(agencyRepository.findById(1)).thenReturn(Optional.of(agency));
agencyService.deleteById(1);
verify(agencyRepository).deleteById(1);
}
What assertion should I make in order to verify that the deletion was successful? The way I've tried it it doesn't work, the result of the test is that an exception is thrown. I'm guessing that the exception is thrown because agencyRepository.findById((int) 1L); basically doesn't exist anymore, but I thought maybe there's a better way to verify the deletion, without searching for the deleted object.

Not the answer, but your method could be written something like this:
public void deleteById(Integer id) {
Agency agency = repo.findById(id).orElseThrow(() -> throw new AgencyNotFoundException("Agency not found"));
repo.deleteById(agency.getId());
}
Because:
Optional<Agency> optionalAgency = repo.findAll().stream().filter(agency -> agency.getId().equals(id)).findFirst(); is not efficient. What if you have thousands of agencies? Will you fetch all and filter through all of them just to find by id. You have that method in your repository already.
if (optionalAgency.isPresent()) {} Optional.isPresent is not good practise. Read here and this answer

repo is a mock, so calling deleteById on it won't actually delete anything. Instead, you could verify that calling the AgencyService's deleteById actually calls AgencyRepository's deleteById.
EDIT:
The repository's code uses findAll, not findById, so you need to make sure you mock that:
#Test
void delete() {
Agency agency = new Agency();
agenct.setId((int) 1L);
when(agencyRepository.findAll()).thenReturn(Collections.singletonList(agency));
agencyService.deleteById((int) 1L);
verify(agencyRepository).delteById((int) 1L);
}

Related

Mockito WrongTypeOfReturnValue: Boolean cannot be returned by findById()

I am trying to test the following method with JUnit tests, using Mockito:
#Override public List<Adoption> search(String username, Integer id) {
List<Adoption> emptySearchResult = new ArrayList<>();
if(id != null && !username.equals("") ) {
if(!this.petRepository.findById(id).isPresent()){
return emptySearchResult;
}
if(!this.appUserRepository.findByUsername(username).isPresent()){
return emptySearchResult;
}
Pet pet = this.petRepository.findById(id).orElseThrow( () -> new PetNotFoundException(id));
AppUser user = this.appUserRepository.findByUsername(username).orElseThrow( () -> new UsernameNotFoundException(username));
return this.adoptionRepository.findAllByUserAndPet(user, pet);
}
else if(id != null && username.equals("")){
if(!this.petRepository.findById(id).isPresent()){
return emptySearchResult;
}
Pet pet = this.petRepository.findById(id).orElseThrow( () -> new PetNotFoundException(id));
return this.adoptionRepository.findAllByPet(pet);
}
else if(id == null && !username.equals("")) {
if(!this.appUserRepository.findByUsername(username).isPresent()){
return emptySearchResult;
}
AppUser user = this.appUserRepository.findByUsername(username).orElseThrow( () -> new UsernameNotFoundException(username));
return this.adoptionRepository.findAllByUser(user);
}
else {
return this.adoptionRepository.findAll();
}
}
However, I run into a problem with the following part:
if(!this.petRepository.findById(id).isPresent())
Even though I have mocked this.petRepository.findById(id), for some reason isPresent() is returning false. This is my initialization for the tests:
#Mock
private AdoptionRepository adoptionRepository;
#Mock
private PetRepository petRepository;
#Mock
private AppUserRepository appUserRepository;
private AdoptionServiceImpl service;
private Adoption adoption1;
private Adoption adoption2;
private Adoption adoption3;
private AppUser user;
private AppUser user2;
private Pet pet;
private Pet petAlteadyAdopted;
List<Adoption> allAdoptions = new ArrayList<>();
List<Adoption> userFilteredAdoptions = new ArrayList<>();
List<Adoption> petFilteredAdoptions = new ArrayList<>();
#Before
public void init() {
MockitoAnnotations.initMocks(this);
user = new AppUser("username","name","lastname","email#gmail.com","pass",ZonedDateTime.now(), Role.ROLE_USER, City.Skopje);
user2 = new AppUser("username1","name","lastname","email#gmail.com","pass",ZonedDateTime.now(), Role.ROLE_USER, City.Skopje);
Center center = new Center("a", City.Bitola,"url");
pet = new Pet("p", Type.DOG,"b", Gender.FEMALE,"d",center, ZonedDateTime.now(),"url",null,false,ZonedDateTime.now());
petAlteadyAdopted = new Pet("p", Type.DOG,"b", Gender.FEMALE,"d",center, ZonedDateTime.now(),"url",null,true,ZonedDateTime.now());
pet.setId(0);
petAlteadyAdopted.setId(1);
adoption1 = new Adoption(ZonedDateTime.now(),ZonedDateTime.now(),Status.ACTIVE,user,pet);
adoption2 = new Adoption(ZonedDateTime.now(),ZonedDateTime.now(),Status.CLOSED,user,pet);
adoption3 = new Adoption(ZonedDateTime.now(),ZonedDateTime.now(),Status.CLOSED,user2,new Pet());
allAdoptions.add(adoption1);
allAdoptions.add(adoption2);
allAdoptions.add(adoption3);
petFilteredAdoptions.add(adoption2);
petFilteredAdoptions.add(adoption1);
userFilteredAdoptions.add(adoption2);
userFilteredAdoptions.add(adoption1);
Mockito.when(this.adoptionRepository.findById(0)).thenReturn(java.util.Optional.of(adoption1));
Mockito.when(this.adoptionRepository.findById(1)).thenReturn(java.util.Optional.of(adoption2));
Mockito.when(this.petRepository.findById(0)).thenReturn(java.util.Optional.of(pet));
Mockito.when(this.petRepository.findById(1)).thenReturn(java.util.Optional.of(petAlteadyAdopted));
Mockito.when(this.appUserRepository.findByUsername("username")).thenReturn(java.util.Optional.of(user));
Mockito.when(this.appUserRepository.findByUsername("username1")).thenReturn(java.util.Optional.of(user2));
Mockito.when(this.adoptionRepository.findAll()).thenReturn(allAdoptions);
Mockito.when(this.adoptionRepository.findAllByPet(pet)).thenReturn(petFilteredAdoptions);
Mockito.when(this.adoptionRepository.findAllByUser(user)).thenReturn(userFilteredAdoptions);
Mockito.when(this.adoptionRepository.findAllByUserAndPet(user,pet)).thenReturn(userFilteredAdoptions);
Mockito.when(this.adoptionRepository.save(Mockito.any(Adoption.class))).thenReturn(adoption1);
this.service = Mockito.spy(new AdoptionServiceImpl(this.adoptionRepository, this.petRepository,this.appUserRepository));
}
As a result, the following test fails, even though it should pass:
#Test
public void searchTest2() {
List<Adoption> adoptionList = this.service.search("",0);
Assert.assertEquals(petFilteredAdoptions,adoptionList);
}
In order to solve this I tried mocking the isPresent() method:
Mockito.when(this.petRepository.findById(0).isPresent()).thenReturn(true);
But I'm getting the following exception:
org.mockito.exceptions.misusing.WrongTypeOfReturnValue:
Boolean cannot be returned by findById() findById() should return
Optional***
If you're unsure why you're getting above error read on. Due to the
nature of the syntax above problem might occur because:
This exception might occur in wrongly written multi-threaded tests. Please refer to Mockito FAQ on limitations of concurrency
testing.
A spy is stubbed using when(spy.foo()).then() syntax. It is safer to stub spies -
with doReturn|Throw() family of methods. More in javadocs for Mockito.spy() method.
I also tried the following variation:
Mockito.doReturn(true).when(this.petRepository.findById(0)).isPresent();
But then I got the following exception:
org.mockito.exceptions.misusing.UnfinishedStubbingException:
Unfinished stubbing detected here:
-> at mk.finki.ukim.milenichinja.ServiceTests.AdoptionServiceFilterTests.init(AdoptionServiceFilterTests.java:87)
E.g. thenReturn() may be missing. Examples of correct stubbing:
when(mock.isOk()).thenReturn(true);
when(mock.isOk()).thenThrow(exception);
doThrow(exception).when(mock).someVoidMethod(); Hints:
missing thenReturn()
you are trying to stub a final method, which is not supported
you are stubbing the behaviour of another mock inside before 'thenReturn' instruction is completed
Any ideas how to solve this problem?
In the init method, you are stubbing findById on the mock instance this.petRepository to return a non-mock Optional, which is good. In your new test, you are trying to set a return value for isPresent, which you can't do because Optional is not a mock. If you want to override the behavior per-test, you'll need to stub findById to return an Optional of a different instance. Therefore, this is right, though it appears exactly as it does in init and consequently it can't tell you why your test is failing.
Mockito.when(this.petRepository.findById(0))
.thenReturn(java.util.Optional.of(pet));
Mockito works by creating a mock object that subclasses a class and overrides every method. The overridden method is what interacts with a static (ThreadLocal) infrastructure, allowing you to use when syntax. The important thing here is that when ignores its argument, and instead tries to mock the last interaction that you made with a mock. You can find out more in the SO questions How does mockito when() invocation work? and How do Mockito matchers work?.
When you see this call:
Mockito.when(this.petRepository.findById(0))
.thenReturn(java.util.Optional.of(pet));
Then it works as you've intended:
petRepository is a mock, findById is presumably an overridable method, Mockito records the fact that you've called it with the argument 0.
findById doesn't have any behavior stubbed yet, so it does its default, returning null.
when doesn't care that it just received null, because the null doesn't tell it anything about what methods were called to get the null. Instead it looks back at its most recent record (findById(0)) and returns an object with the thenVerb methods you expect.
You call thenReturn, so Mockito sets up petRepository to return the Optional instance you created and passed in.
But when you try this call:
Mockito.when(this.petRepository.findById(0).isPresent()).thenReturn(true);
Then the most recent interaction isn't isPresent, it's findById, so Mockito assumes you want findById(0) to thenReturn(true) and throws WrongTypeOfReturnValue. Optional is not a mock, so interacting with it doesn't let Mockito record its interaction or replay your behavior. For what it's worth, I also wouldn't advise mocking it: Optional is a final class, and though Mockito has recently added some support for mocking final types, Optional is simple and straightforward enough that it makes more sense to just return the Optional instance you want rather than trying to mock it.
With all that said, your code looks right; as long as PetRepository is an interface I don't see anything about the way your method looks or the way your mocks look that would cause this.petRepository.findById(0) to return an absent Optional. In fact, I don't even see where you would create an absent Optional for it to return, so I can only guess that you are using more real objects in your test than you think you are.

JUnit test to retrieve all entities fails with actual result '[]'

I am trying to test my method for getting back all entities that exist in my db. I am using JUnit and Mockito. I have no experience with testing so far and this is how far I've got:
This is my method from the agency service to get back all entities, using the findAll() function of JpaRepository:
public List<AgencyDto> getAll() {
return repo.findAll().stream().map(agency -> mapper.mapToDto(agency)).collect(Collectors.toList());
}
#ExtendWith(MockitoExtension.class)
public class AgencyServiceTest {
#Mock
private AgencyRepository agencyRepository;
#InjectMocks
private AgencyService agencyService;
#Test
void getAgencies() {
List<Agency> existingAgencies = new ArrayList<Agency>();
Agency agency1 = new Agency();
Agency agency2 = new Agency();
existingAgencies.add(agency1);
existingAgencies.add(agency2);
when(agencyRepository.findAll()).thenReturn(existingAgencies);
List<AgencyDto> result = agencyService.getAll();
assertEquals(existingAgencies, result);
}
}
When running the test, the value for expected seems ok, but the value for actual is an empty array:
Expected :[com.project.DTOs.AgencyDto#245a26e1, com.project.DTOs.AgencyDto#4d63b624, com.project.DTOs.AgencyDto#466cf502]
Actual :[]
Is this not the right way to test get() methods? Am I doing something wrong when setting the actual result?
if i understand your code coreectly
agencyRepository.findAll() return List<Agency>
agencyService.getAll() return List<AgencyDto>
Issue is here
List<Agency> existingAgencies = new ArrayList<Agency>();
when(agencyRepository.findAll()).thenReturn(existingAgencies);
your mock returns an empty list, you need to add items to list
eg:
List<Agency> existingAgencies = new ArrayList<Agency>();
existingAgencies.add(agencyObjectInDB1);
existingAgencies.add(agencyObjectInDB2);
existingAgencies.add(agencyObjectInDB3);
//Mock repo call
when(agencyRepository.findAll()).thenReturn(existingAgencies);
//I assume Here inside getAll() , agencyRepository.findAll() is invoked
//getAll() converts Agency to AgencyDto
List<AgencyDto> result = agencyService.getAll();
// assert against pre-pepared list
assertEquals(agencies, result);

How to check is there a row with getOne function

Assume that "Project" is POJO. In service layer of my project, I wrote a function that is get a row from table.
#Override
public ProjectDto getAProject(Long id) {
try {
Project project = projectRepository.getOne(id);
if (project==null) {
throw new IllegalArgumentException("Project not found");
} else {
return modelMapper.map(project, ProjectDto.class);
}
} catch (EntityNotFoundException ex) {
throw new IllegalArgumentException("Project not found");
}
}
The function is working fine with already exist id values. But if I give non-exist value, an exception occur like following. Looks like "getOne()" function don't throw "EntityNotFoundException".
ModelMapper mapping errors: Error mapping com.issuemanagement.entity.Project to com.issuemanagement.dto.ProjectDto
that means the exception come from model mapper logic. Because "project" object filled with null values so couldn't map to DTO class. I modified the function as following to fix this.
#Override
public ProjectDto getAProject(Long id) {
boolean isExist = projectRepository.existsById(id);
if (isExist) {
Project project = projectRepository.getOne(id);
return modelMapper.map(project, ProjectDto.class);
} else {
throw new IllegalArgumentException("Project not found");
}
}
but in this way the program goes to DB for two times. I don't like it. How can I do this operation with just one transaction?
BTW, if I try to run "toString()" function of "project", it throw "EntityNotFoundException" but it's looks like not official way. or it is? I hope there should be a boolean variable in somewhere.
getOne() on JpaRepository will call getReference() on EntityManager under the hood which will return an instance whose state is lazily fetch .The EntityNotFoundException will not throw immediately but will only be thrown when its state is accessed at the first time .
It is normally used in the case that when you need to configure a #ManyToOne relationship for an entity (Let say configure a Department for an Employee) but you just have the ID of the related entity.(e.g. DepartmentId) . Using getOne() allows you to get a Department proxy instance such that you do not really need to query the database to get the actual Department instance just for setting up its relationship for an Employee.
In your case , you should use findById() which will return an empty Optional if the instance does not exist:
#Override
public ProjectDto getAProject(Long id) {
Project project = projectRepository.findById(id)
.orElseThrow(()->new IllegalArgumentException("Project not found"));
return modelMapper.map(project, ProjectDto.class);
}

How can I test a constraint violation when performing a delete in a Spring integration test?

I want to test that my controller endpoint returns an appropriate error code when trying to delete a record with referencing child records. In my integration test, I need to set up the state so that the related records exist, then invoke the deletion endpoint, expect the error condition, and then (ideally) roll the entire DB back to the state it was in before the test.
e.g.
INSERT INTO parent_rec (id) VALUES ("foo");
INSERT INTO child_rec (id, parent_id) VALUES ("bar", "foo");
COMMIT;
DELETE FROM parent_rec WHERE id = "foo"; -- bang!
#PersistenceContext
EntityManager em;
#Transactional
void testDelete() {
// Set up records
ParentRecord record = new ParentRecord("foo");
em.persist(record);
em.persist(new ChildRecord("bar", record));
//delete
mockMvc.perform(delete("/parent/foo")).andExpect(/* some error code */);
}
However, I'm running into issues. If I put the #Transactional annotation at the method or class level, the records aren't persisted until after the deletion is attempted so the deletion returns a 200 OK rather than a 400 Bad Request or similar.
The current solution is for the tests to be run in order (with a previous test setting up records which a subsequent test tries to operate on). However, this makes the tests pretty brittle and dependent on each other, which I'd like to avoid primarily to make changing the code easier.
Can I accomplish what I want without using an additional layer of tooling? In the past, I'd have used DBUnit to do something like this, but if I can avoid adding the additional dependency I'd prefer to keep it simple.
In JEE I solved these issues kind of simply by splitting my code into two parts:
#Transactional(propagation = Propagation.REQUIRES_NEW)
public class ParentRecordTestFacade {
public void create() {
// Create record here
}
public void delete() {
// Delete record here
}
}
and then call both methods in the actual unit test one after another.
Running only some code in a separate transaction also comes in handy. You can achieve it for example by creating a method fo the block of code to invoke in transaction:
protected <T> T getInsideTransaction(Function<EntityManager, T> transactional) {
EntityManager em = null;
EntityTransaction trx = null;
try {
em = entityManagerFactory.createEntityManager();
trx = em.getTransaction();
trx.begin();
return transactional.apply(em);
} catch (Throwable throwable) {
throw throwable;
} finally {
if (trx != null) {
if (!trx.getRollbackOnly()) {
trx.commit();
} else {
trx.rollback();
}
}
if (em != null) {
em.close();
}
}
}
Now you can invoke it like that:
void testDelete() {
// Set up records
getInsideTransaction(em -> {
ParentRecord record = new ParentRecord("foo");
em.persist(record);
em.persist(new ChildRecord("bar", record));
}
//delete
mockMvc.perform(delete("/parent/foo")).andExpect(/* some error code */);
}
You can invoke an arbitrary block of code within separate transaction that way.
In spring especially for test such cases in repository layer I using, looks like should works and for you - org.springframework.test.context.transaction.TestTransaction. Pay attention on #Commit annotation on test method, otherwise your record will not be saved.
#Commit
void testDelete() {
// Set up records
ParentRecord record = new ParentRecord("foo");
em.persist(record);
em.persist(new ChildRecord("bar", record));
TestTransaction.end()
TestTransaction.start()
//delete
mockMvc.perform(delete("/parent/foo")).andExpect(/* some error code */);
}
But of course after commit you should delete manually you record.

Data is autodeleted from in-memory database

I use HSQLDB for testing purpose. The problem is that when I add some data in init() method, then I can get that data only from the test which did run first.
#Before
public void init() {
if(isRun)
return;
isRun = true;
Role role = new Role();
role.setId(1);
role.setType("User");
roleDAO.save(role);
User user = new User();
user.setCredits(1000);
user.setEmail("User#test.com");
user.setUsername("User");
user.setPassword("qwerty");
user.setRoles(new HashSet<Role>(Arrays.asList(roleDAO.findById(1))));
userDAO.save(user);
User user2 = new User();
user2.setCredits(1000);
user2.setEmail("User2#test.com");
user2.setUsername("User2");
user2.setPassword("qwerty");
user2.setRoles(new HashSet<Role>(Arrays.asList(roleDAO.findById(1))));
userDAO.save(user2);
}
#Test
public void findUserByIdTest() {
User user = userDAO.findByUsername("User");
assertEquals(userDAO.findById(user.getId()), user);
}
#Test
public void addUserTest() {
User user = new User();
user.setCredits(1000);
user.setEmail("Antony#test.com");
user.setPassword("qwerty");
user.setUsername("Antony");
user.setRoles(new HashSet<Role>(Arrays.asList(roleDAO.findById(1))));
userDAO.save(user);
assertEquals(userDAO.findByUsername("Antony"), user);
}
#Test
public void updateUserTest() {
User user = userDAO.findByUsername("User");
user.setCredits(0);
assertEquals(userDAO.findByUsername("User").getCredits(), (Integer) 0);
}
#Test
public void removeUserTest() {
userDAO.remove(userDAO.findByUsername("User"));
assertNull(userDAO.findByUsername("User"));
}
So happens that removeUserTest() method always runs first and when I findAll() data then I see the data I set in init() method. After that, others test methods run but if I do findAll() there, it just returns nothing meaning no data exists.
In addition, I have set hibernate.hbm2ddl.auto=create.
What am I missing here? Why I can get data in the first running method but in others the data just disappears.
It's expected: Spring repository tests are transactional and the transaction is rollbacked at the end of each test by default.
Even if you choose not to rollback, every test should be independant from the others, and should be able to run alone. You should not rely on the execution order either. Your findUserByIdTest() would fail if removeUserTest() runs first.
So, start by cleaning the database and to insert the test data before each test. If you let Spring rollback after each test, cleaning is not necessary, but you should still insert the test data before each test.
Incrementing IDs should not be a problem: you just need to stire the created entities or their IDs in fields of the test, and refer to these entities and their IDs instead of using hard-coded IDs in the test.

Categories