Issue
I would like to understand and see the easiest way to create a Unit Test for my Service Layer. I've been researching for days on the easiest way to do this, but unfortunately I still haven't found a solution for my specific code. I tried to write the test many times myself, but people told me that the code could be done much better. Thank you in advance.
My Entity:
@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Builder
@Table
public class Appointment {
@Id
@SequenceGenerator(
name = "appointment_sequence",
sequenceName = "appointment_sequence",
allocationSize = 1
)
@GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "appointment_sequence"
)
private Long appointmentId;
@Column(
name = "date_of_appointment",
nullable = false
)
private LocalDate dateOfAppointment;
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(
name = "patient_id",
referencedColumnName = "patientId"
)
private Patient patient;
public void connectWithPatient(Patient patient) {
this.patient = patient;
}
}
My Repository:
public interface AppointmentRepository extends JpaRepository<Appointment, Long> {
}
And my Service:
@Service
public class AppointmentService implements AppointmentServiceInterface {
private final AppointmentRepository appointmentRepository;
private final PatientRepository patientRepository;
@Autowired
public AppointmentService(AppointmentRepository appointmentRepository, PatientRepository patientRepository) {
this.appointmentRepository = appointmentRepository;
this.patientRepository = patientRepository;
}
@Override
public List<Appointment> getAllAppointments() {
return appointmentRepository.findAll();
}
@Override
public ResponseEntity<Appointment> getAppointmentById(Long appointmentId) {
Appointment appointment = appointmentRepository.findById(appointmentId)
.orElseThrow(()-> new ResourceNotFoundException("Appointment with id " + appointmentId + " doesn't exist."));
return ResponseEntity.ok(appointment);
}
@Override
public Appointment createNewAppointment(Appointment appointment) {
return appointmentRepository.save(appointment);
}
@Override
public ResponseEntity<Appointment> updateAppointment(Long appointmentId, Appointment appointmentUpdatedDetails) {
Appointment updatedAppointment = appointmentRepository.findById(appointmentId)
.orElseThrow(()-> new ResourceNotFoundException("Appointment with id " + appointmentId + " doesn't exist."));
updatedAppointment.setDateOfAppointment(appointmentUpdatedDetails.getDateOfAppointment());
appointmentRepository.save(updatedAppointment);
return ResponseEntity.ok(updatedAppointment);
}
@Override
public ResponseEntity<Appointment> deleteAppointment(Long appointmentId) {
Appointment appointment = appointmentRepository.findById(appointmentId)
.orElseThrow(()-> new ResourceNotFoundException("Appointment with id " + appointmentId + " doesn't exist."));
appointmentRepository.delete(appointment);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
@Override
public Appointment makeAppointmentWithPatient(Long appointmentId, Long patientId) {
Appointment appointment = appointmentRepository.findById(appointmentId)
.orElseThrow(()-> new ResourceNotFoundException("Appointment with id " + appointmentId + " doesn't exist."));
Patient patient = patientRepository.findById(patientId)
.orElseThrow(()-> new ResourceNotFoundException("Patient with id " + patientId + " doesn't exist."));
appointment.connectWithPatient(patient);
return appointmentRepository.save(appointment);
}
}
Solution
Returning ResponseEntity
is not something you would really like to do from the service layer - it's a place for the business logic and not for Web responses. So currently your service is bound to spring MVC.
On the positive side, it doesn't call the Database directly and instead has DAOs injected as dependencies.
So the real question is what would you like to test here? If you want to test the service itself, it can be unit-tested with a simple unit test, you don't need spring here:
- Create mocks for the Repositories and inject them into the instance of the service which will be a "subject" of your test as if they're real DAOs.
- Then specify expectations like "if someone calls
findAll
then return a list of entity objects. Again, there is no need to use any kind of database here - Verify the expected return result from the method of the service that you test with some assertion library
That's it.
Of course, if you'll return real objects rather than ResponseEntity
from some methods your code will be way cleaner and hence the testing will be easier because you'll verify the real domain objects rather than web-mvc abstractions.
Answered By - Mark Bramnik
Answer Checked By - Candace Johnson (JavaFixing Volunteer)