Hi have read a lot about this but can't come to a conclusion about the best way to test a method that is dependent on other method call results to perform its actions.
Some of the questions I've read include:
Testing methods that depend on each other
Unit testing a method that calls other methods
Unit testing a method that calls another method
Some of the answers sugest that we should only test the methods that perform only one action and then test the method that call this methods for conditional behaviuour (for example, verifying if a given method was called or not) and that's fine, I get it, but I'm struggling with other scenario.
I have a service with a REST api.
The controller has a create method that receives a DTO and calls the Service class create method with this argument (DTO).
I'm trying to practice TDD and for this I use this project I'm building without a database.
The code is as follows:
#Service
public class EntityService implements FilteringInterface {
private MemoryDatabase db = MemoryDatabase.getInstance();
//Create method called from controller: receives DTO to create a new
//Entity after validating that it's name and code parameters are unique
public EntityDTO create(EntityDTO dto) throws Exception {
validateUniqueFields(dto);
Entity entity = Entity.toEntity(dto, "id1"); //maps DTO to Entity object
db.add(entity);
return new EntityDTO.Builder(entity);//maps entity to DTO
}
public void validateUniqueFields(EntityDTO dto) throws Exception {
Set<Entity> foundEntities = filterEntityByNameOrCode(dto.getName(),
dto.getCode(), db.getEntities());
if (!foundEntities.isEmpty()) {
throw new Exception("Already exists");
}
}
}
This is the interface with methods reused by other service classes:
public interface FilteringInterface {
default Set<Entity> filterEntityByNameOrCode(String name, String code, Set<Entity> list) {
return list.stream().filter(e -> e.getSiteId().equals(siteId)
&& (e.getName().equals(name)
|| e.getCode().equals(code))).collect(Collectors.toSet());
}
default Optional<Entity> filterEntityById(String id, Set<Entity> list) {
return list.stream().filter(e -> e.getId().equals(id)).findAny();
};
}
So, I'm testing this service class and I need to test the create() method because it can have different behaviors:
If the received DTO has a name that already exists on the list of entities -> throws Exception
If the received DTO has a code that already exists on the list of entities -> throws Exception
If the received DTO has a name and a code that already exists on the list of entities -> throws Exception
If name and code are different, than everything is ok, and creates the entity -> adds the entity to the existing list - > converts the entity to DTO and retrieves it.
Problem:
To test any of the scenarios, suppose, scenario 1: I need to make the filterEntityByNameOrCode() method return a list with an Entity that has the same name as the Entity I'm trying to create. This method is called inside validateUniqueFields() method.
Problem is: I can't call mockito when() for any of this methods because, for that, I would have to mock the service class, which is the class that I'm testing and, thus, it's wrong approach.
I've also read that using Spy for this is also wrong approach.
So, where thus that leaves me?
Also: if this code is not the correct aprocah, and thats why
it can't be correctly tested, than, whats should the correct approach be?
This service will have other methods (delete, update, etc.). All of this methods will make use of the FilteringInterface as well, so I will have the same problems.
What is the correct way of testing a service class?
I would apply an DI pattern in your service, in order to mock and control the db variable.
#Service
public class EntityService implements FilteringInterface {
private Persistence db;
public EntityService(Persistence db) {
this.db = db;
}
}
After that, you will be able to add entities to Set accordingly to your scenarios
#ExtendWith(MockitoExtension.class)
class EntityServiceTest {
#Mock
private Persistence persistence;
#InjectMocks
private EntityService entityService;
#BeforeEach
void before() {
final Set<Entity> existentEntity = Set.of(new Entity(1L,1L, "name", "code"));
when(persistence.getEntities()).thenReturn(existentEntity);
}
#Test
void shouldThrowWhenNameAlreadyExists() {
final EntityDTO dto = new EntityDTO(1L, "name", "anything");
assertThrows(RuntimeException.class, () -> entityService.create(dto));
}
#Test
void shouldThrowWhenCodeAlreadyExists() {
final EntityDTO dto = new EntityDTO(1L, "anything", "code");
assertThrows(RuntimeException.class, () -> entityService.create(dto));
}
#Test
void shouldThrowWhenNameAndCodeAlreadyExists() {
final EntityDTO dto = new EntityDTO(1L, "name", "code");
assertThrows(RuntimeException.class, () -> entityService.create(dto));
}
#Test
void shouldNotThrowWhenUnique() {
final EntityDTO dto = new EntityDTO(1L, "diff", "diff");
final EntityDTO entityDTO = entityService.create(dto);
assertNotNull(entityDTO);
}
}
Related
I am new in Junit tests and I have a question about it. Here you can see the method findById in my service class:
#Service
public class DefaultQuarterService implements QuarterService {
private final QuarterRepository quarterRepository;
public DefaultQuarterService(QuarterRepository quarterRepository) {
this.quarterRepository = quarterRepository;
}
#Override
public QuarterEntity findById(int id) {
return quarterRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException(String.format("Quarter does not exist for id = %s!", id)));
}
}
And here is my QuarterRepository:
#Repository
public interface QuarterRepository extends CrudRepository<QuarterEntity, Integer> {
}
And here is my Junit implementation for this method:
#MockBean
private QuarterRepository quarterRepository;
#Test
public void throwExceptionWhenQuarterIdNotFound() {
int id = anyInt();
when(quarterRepository.findById(id))
.thenReturn(Optional.empty());
assertThatAnExceptionWasThrown(String.format("Quarter does not exist for id = %s!", id));
}
public void assertThatAnExceptionWasThrown(
String errorMsg
) {
expectException.expect(RuntimeException.class);
expectException.expectMessage(errorMsg);
}
Unfortunately test doesn't pass. Here the error in terminal:
java.lang.AssertionError: Expected test to throw (an instance of
java.lang.RuntimeException and exception with message a string
containing "Quarter does not exist for id = 0!")
Maybe it is so simple but I can not see what I am missing. I would be so happy if you can direct me. Thanks a lot!
As you mock your Repository it will return with Optional.empty() correctly, I think you should call your service's (which is Autowired) findById method. It will throw the exception actually.
First issue
In the assertThatAnExceptionWasThrown method you expect RuntimeException BUT in the service class you throw EntityNotFoundException, So I guess you should expect EntityNotFoundException in your test case.
Second issue
After this part of the code.
when(quarterRepository.findById(id))
.thenReturn(Optional.empty());
Why didn't you call your service method (findById)?
When you are returning the empty value, you should verify your condition with the service method you want to test it.
It should be something like this.
assertThatThrownBy(() -> defaultQuarterService.findById(id))
.isInstanceOf(ApiRequestException.class)
.hasMessageContaining("PUT_YOUR_EXCEPTION_MESSAGE_HERE");
This is a good sample for unit-test in the spring boot. You can check it out. Link
Try the above solutions and let me know it has been fixed or not. Good luck
Say I've got an entity class Person and a controller PersonController. I've got a custom REST endpoint I want to implement and can not use a CrudRepository method for.
This is what my PersonController looks like:
#RepositoryRestController
#RequestMapping("/people")
public class PersonController {
#Autowired
private PeopleRestResource peopleRestResource; //#RepositoryRestResource extending CrudRepository
#GetMapping("/custom")
public ResponseEntity<?> getCustomPeople(PersistentEntityResourceAssembler persistentEntityResourceAssembler) {
Set<Person> people = stream(this.peopleRestResource.findAll().spliterator(), true)
.filter(/*Filter logic*/)
.collect(toSet());
return ok(persistentEntityResourceAssembler.toFullResource(people));
}
}
This will throw an IllegalArgumentException with the message PersistentEntity must not be null. people will actually contain a set of 2 person objects so this error message was a bit confusing at first. However, I assume this message actually means Set is not a persistent entity, as if I were to return just one person, the code would run just fine.
#GetMapping("/custom")
public ResponseEntity<?> getCustomPeople(PersistentEntityResourceAssembler persistentEntityResourceAssembler) {
Person person = stream(this.peopleRestResource.findAll().spliterator(), true)
.filter(/*Filter logic*/)
.findFirst()
.elseThrow(() => new IllegalStateException());
return ok(persistentEntityResourceAssembler.toFullResource(person));
}
Is there a way to make use of the PersistentEntityResourceAssembler to construct a HAL resource for a list of entities?
Preferably I wouldn't want to construct a Resources object and constructing all the links myself.
To return a list of entities you can use CollectionModel
https://docs.spring.io/spring-hateoas/docs/current/reference/html/#fundamentals. You can call toCollectionModel() on the PersistentEntityResourceAssembler.
#GetMapping("/custom")
public ResponseEntity<?> getCustomPeople(PersistentEntityResourceAssembler persistentEntityResourceAssembler) {
List<Person> persons = this.peopleRestResource.findAll()
return ok(persistentEntityResourceAssembler.toCollectionModel(persons));
}
I'm not sure where to open my Transaction object. Inside the service layer? Or the controller layer?
My Controller basically has two services, let's call them AService and BService. Then my code goes something like:
public class Controller {
public AService aService = new AService();
public BService bService = new BService();
public void doSomething(SomeData data) {
//Transaction transaction = HibernateUtil.getSession().openTransaction();
if (data.getSomeCondition()) {
aService.save(data.getSomeVar1());
bService.save(data.getSomeVar2());
}
else {
bService.save(data.getSomeVar2());
}
//transaction.commit(); or optional try-catch with rollback
}
}
The behavior I want is that if bService#save fails, then I could invoke a transaction#rollback so that whatever was saved in aService would be rolled back as well. This only seems possible if I create one single transaction for both saves.
But looking at it in a different perspective, it looks really ugly that my Controller is dependent on the Transaction. It would be better if I create the Transaction inside the respective services, (something like how Spring #Transactional works), but if I do it that way, then I don't know how to achieve what I want to happen...
EDIT: Fixed code, added another condition. I am not using any Spring dependencies so the usage of #Transactional is out of the question.
You can accomplish what you're asking with another layer of abstraction and using composition.
public class CompositeABService {
#Autowired
private AService aservice;
#Autowired
private BService bservice;
#Transactional
public void save(Object value1, Object value2) {
aservice.save( value1 );
bservice.save( value2 );
}
}
public class AService {
#Transactional
public void save(Object value) {
// joins an existing transaction if one exists, creates a new one otherwise.
}
}
public class BService {
#Transactional
public void save(Object value) {
// joins an existing transaction if one exists, creates a new one otherwise.
}
}
This same pattern is typically used when you need to interact with multiple repositories as a part of a single unit of work (e.g. transaction).
Now all your controller needs to depend upon is CompositeABService or whatever you wish to name it.
I am trying to test the method findById() method in the class below that reads data from my Database using the CrudRepository:
Class under test:
public interface PersonRepository extends CrudRepository<Person, Integer>
{
Person findById(String id);
}
Below is my test class, the test is currently passing but I would like to change it so that if the id "1" I am testing with is removed from my database, I can still run my test. I.e. do not rely on data within the database.
How can I do so?
Test Class:
public class PersonRepositoryTest {
#Mock
private PersonRepository personRepository;
#Before
public void setUp() throws Exception {
//Initialize the mocked class and ensure that it is not null
MockitoAnnotations.initMocks(this);
assertThat(personRepository, notNullValue());
}
#Test
public void testFindById() throws ParseException {
//test string
String id = "1";
//when it is called, return
when(personRepository.findById(anyString())).thenReturn(new Person());
Person person = personRepository.findById(id);
assertThat(person, notNullValue());
}
}
As mentioned in the Post comments by #Thomas, you are just mocking the database. I'm assuming you want to write a negative test case when the ID is 1.
You can just return null, instead of person Object. Instead of Matchers, pass a specific value to differentiate your positive and negative test cases.
Positive Case -
when(personRepository.findById(2)).thenReturn(new Person());
Negative Case -
when(personRepository.findById(1)).thenReturn(null);
A very simple use case implemented using DDD and java.
I have a FooEntity and a FooRepository. The Entity has a delete method which validates certain state to check whether it is safe to be deleted, and in case this evaluates to true invoke the delete in the repository, which is injected in the entity.
So far so good, but, what happens if somebody invokes the delete method directly in the repository? Then the validation wouldn't be performed.
Placing the validation in the repository would solve the problem, but this would be clearly wrong since it would make necessary to expose the internal state of the entity.
What am I missing?
public class FooEntity {
#inject
FooRepository fooRepository;
private Boolean canBeDeleted;
public void delete(){
if (canBeDeleted){
fooRepository.delete(this);
}
throw new CannotBeDeletedException();
}
}
public class FooRepository {
#inject
FooDAO fooDAO;
public void delete(FooEntity fooEntity){
fooDAO.delete(fooEntity.getId());
}
}
Don't expose the internal state, expose a method like isDeletable() on the entity. The repository's delete can call entity.isDeletable() before deleting, and raise an exception if you are trying to delete an entity that is not deletable. That way you separate the concerns. The entity has the domain knowledge of it's "deletableness", while the repo knows how to delete the entity.
The example code is fine as is (except that it's strange to have a DAO inside a repository class, as "repository" is just a more abstract name for the same concept as the DAO).
You can't really prevent other developers from calling the wrong methods, except for using static analysis code inspections where available.
The repository should only concern itself with removing the given entity instance from the set of persistent entities. It cannot have logic for checking whether the entity is allowed to be deleted or not, even if the isDeletable() method is in the entity class.
I would put the delete functionality in a domain service.
public class FooService {
#inject
FooRepository fooRepository;
public void delete(Foo foo) {
if( /* insert validation stuff here to check if foo can be deleted */ ) {
fooRepository.delete(foo);
}
}
The way I do it though is I typically use a ValueObject to represent an Entity's identity. E.g.
public class FooId() {
String foodId;
public String FooId(String fooId) {
this.foodId = foodId;
}
}
public class Foo() {
FooId id;
/* other properties */
}
I would then revise FooService to:
public class FooService {
#inject
FooRepository fooRepository;
public void delete(FooId fooId) {
foo = fooRepository.retrieve(fooId);
if( /* insert validation stuff here to check if foo can be deleted */ ) {
fooRepository.delete(foo);
}
}
To delete a foo (assuming fooId was passed by a command from the UI:
fooService.delete(fooId);
I would not inject a FooRepository in an a class that represents entity. I don't think that it is the rightful place. An Entity for me should not be able to create or delete itself. These functions should be in a Domain Service for that Entity.