What is the service or repository to test? - spring

I have a custom query in the repository.
And the service that calls this method from the repository.
I want to write a unit test.
What should I test for a repository or a service?
Or should I do 2 absolutely identical tests?

You should unit test everything. Test happy-paths + sad-paths. The more you test the better your results will be.
For repo tests it should be enough to test 1 happy and 1 sad (either you receive the data or you don't)
Service tests require more time, say you have a few if-conditions then you would want to test those specific cases as well.
As an example:
#DataJpaTest
class RoleRepoTest {
#Autowired
private RoleRepo roleRepo;
#PersistenceContext
private EntityManager entityManager;
#Test
void itShouldfindRoleByName() {
// given
Role role = new Role(null, "administrator");
roleRepo.save(role);
flushAndClear();
// when
Role roleExpected = roleRepo.findByName("administrator");
// then
assertThat(roleExpected.getName()).isEqualTo(role.getName());
}
#Test
void itShouldNotfindRoleByName() {
// when
Role expectedRole = roleRepo.findByName("name");
assertThat(expectedRole).isNull();
}
private void flushAndClear() {
entityManager.flush();
entityManager.clear();
}
}
Make sure to always flush & clear. This will avoid your tests from failing when running all at once. It ensures that each test is run in a clean and isolated environment. Without flushing and clearing the database, tests may be affected by data that was created or modified during previous tests.
For Service tests I've got the following example:
#Test
void deleteUser_givenUser_whenTryingToDeleteSameUser_thenUserDeleted() {
// given
given(securityContext.getAuthentication().getPrincipal()).willReturn(userDetails);
given(userDetails.getUsername()).willReturn(user.getUsername());
given(userRepo.findByUsername(user.getUsername())).willReturn(user);
userIsStudent();
String username = user.getUsername();
// when
authService.deleteUser(new DeleteAccountDto(username));
// then
verify(userRepo).deactivateUser(username);
}
#Test
void deleteUser_givenLoggedInUserNotAdmin_whenTryingToDeleteUserWithOtherUsername_ThrowError() {
// given
String username = studentUser.getUsername();
// when + then
assertThatThrownBy(() -> authService.deleteUser(new DeleteAccountDto(username)))
.isInstanceOf(AccountException.class)
.hasMessage("De gebruikersnaam is niet gevonden.");
verify(userRepo, never()).deactivateUser(username);
}
#Test
void deleteUser_givenLoggedInUserIsAdmin_whenTryingToDeleteExistingUser_ThenUserIsDeleted() {
given(securityContext.getAuthentication().getPrincipal()).willReturn(userDetails);
given(userDetails.getUsername()).willReturn(user.getUsername());
userIsAdmin();
// given
given(userRepo.findByUsername(studentUser.getUsername())).willReturn(studentUser);
String username = studentUser.getUsername();
// when
authService.deleteUser(new DeleteAccountDto(username));
// then
verify(userRepo).deactivateUser(username);
}
#Test
void deleteUser_givenLoggedInUserIsAdmin_WhenTryingToDeleteLoggedInUser_ThenThrowError() {
given(securityContext.getAuthentication().getPrincipal()).willReturn(userDetails);
given(userDetails.getUsername()).willReturn(user.getUsername());
userIsAdmin();
// when + then
given(userRepo.findByUsername(adminUser.getUsername())).willReturn(adminUser);
assertThatThrownBy(() -> authService.deleteUser(new DeleteAccountDto(adminUser.getUsername())))
.isInstanceOf(AccountException.class)
.hasMessage("Administrators can't be deleted");
}
#Test
void deleteUser_GivenLoggedInUserIsAdmin_WhenTryingToDeleteOtherAdmin_ThenThrowError() {
userIsAdmin();
given(securityContext.getAuthentication().getPrincipal()).willReturn(userDetails);
given(userDetails.getUsername()).willReturn(user.getUsername());
given(userRepo.findByUsername(otherAdminUser.getUsername())).willReturn(otherAdminUser);
// when + then
assertThatThrownBy(() -> authService.deleteUser(new DeleteAccountDto(otherAdminUser.getUsername())))
.isInstanceOf(AccountException.class)
.hasMessage("Admins can't be deleted");
}
Same thing applies for custom queries. Just try to test all your logic in your service-class
I hope this answers your question.

Related

Spring Boot Testing with Mockito and ExpectedException - console printing 'null' is normal?

I'm writing some test classes for my services and noticed a weird behavior that happens after test results are returned. The console prints 'null' messages and no other information about tests. The tests work fine as I've tried to fail them to ensure that is the case. Is this the expected behavior?
#RunWith(MockitoJUnitRunner.class)
public class GradeServiceTest {
#Rule
public ExpectedException thrown = ExpectedException.none();
#Mock
private GradeRepository gradeRepository;
#Mock
private AssignmentService assignmentService;
#InjectMocks
private GradeService gradeService;
private Assignment assignment;
private Grade grade;
#Before
public void init() {
assignment = new Assignment.Builder()
.withId(0L)
.withModuleId(0L)
.withName("Test assignment")
.withWeightEnabled(false)
.withWeight(0)
.build();
grade = new Grade.Builder()
.withId(0L)
.withAssignmentId(0L)
.withGradeType(GradeType.PERCENTAGE)
.withGradeValue("50%")
.build();
}
#Test
public void shouldAddGrade() throws AssignmentException, GradeException {
// GIVEN
when(assignmentService.exists(grade.getAssignmentId())).thenReturn(true);
when(gradeRepository.insert(any())).thenReturn(grade);
// WHEN
Grade addedGrade = gradeService.addGrade(grade.getAssignmentId(), grade.getType().name(), grade.getValue());
// THEN
assertThat(addedGrade).isEqualTo(grade);
}
#Test
public void shouldNotAddGradeIfAssignmentDoesNotExist() throws AssignmentException, GradeException {
// GIVEN
when(assignmentService.exists(grade.getAssignmentId())).thenReturn(false);
// EXPECT
thrown.expect(AssignmentException.class);
thrown.expectMessage(AssignmentErrorMessages.NOT_FOUND);
// THEN
gradeService.addGrade(grade.getAssignmentId(), grade.getType().name(), grade.getValue());
}
}
I don't think this is normal behavior for each test to be printing 'null' without any other information. Could someone help me understand what is wrong with the code?
Test results:
null
null
Process finished with exit code 0

how to let spring data mongodb to execute createIndexes before every test method?

A field of MongoDB Entity MyCardDO, explicitly set it to unique
#Indexed(unique=true)
private String uid;
and there is a MyCardService to crud MyCardDO, and there is a MyCardServiceTest to test MyCardService, there is a add_repeat_uid_record_failed inner MyCardServiceTest to test the uid cannot be duplicated,
MyCardDO myCardDO1 = new MyCardDO();
myCardDO1.setUid("1");
myCardService.add(myCardDO1);
try {
MyCardDO myCardDO2 = new MyCardDO();
myCardDO2.setUid("1");
myCardService.add(myCardDO2);
Assert.fail();
} catch (DuplicateKeyException e) {
assertTrue(e.getMessage().contains("E11000 duplicate key error collection: opportunity-test.pro_mycard index: uid dup key: { : \"1\" }"));
}
If I run this test method directly it's OK, but I run the whole MyCardServiceTest this method is failed, and from Wireshark I know the createIndexes only executed once, if dropped the collection it will not createIndexes again
#After
public void tearDown() {
mongoTemplate.dropCollection(MyCardDO.class);
}
So how to let spring to execute createIndexes before every test method? that is
#Before
public void setUp() {
// how to auto execute createIndexes before every test method
// prepare some test data
myCardService.add(myCardDO1);
}
p.s.
#RunWith(SpringRunner.class)
#DataMongoTest(includeFilters = #ComponentScan.Filter(type= FilterType.ASSIGNABLE_TYPE,value={MyCardService.class}))
#ActiveProfiles("test")
#Import(SpringMongoConfig.class)
public class MyCardServiceTest {
//...
}
Wireshark screenshot
Final my resolution :
#After
public void tearDown() {
mongoTemplate.remove(new Query(), MyCardDO.class);
}
#AfterClass
public static void finalClean() {
mongoTemplate.dropCollection(MyCardDO.class);
}
that is after finished every test method only delete all records and at final when the whole test class is finished to drop the collection.

How to reuse a scenario in citrus framework?

I set this testing scenario with citrus framework. Now I'm trying to reuse it in other scenarios.
I'm creating a behavior for each step. My behaviors are mainly http requests
public class NoProductDocumentValidationScenarioIT {
private #CitrusResource TestContext parentContext;
#CitrusEndpoint(name = "todoBasicAuthClient")
private HttpClient cmsAuthClient;
#CitrusEndpoint(name = "vdmBasicAuthClient")
private HttpClient vdmAuthClient;
#CitrusEndpoint(name = "gvHttpClient")
private HttpClient gvHttpClient;
#Test
#CitrusTest
public String NoProductDocumentValidation(#CitrusResource TestRunner runner, #CitrusResource TestContext context)
throws BadNewsMLG2Exception {
String pdtIdentifier = "EDIT-FR-SVID2-YM9N001479";
String videoDocument = VideoDocument.setUpVideoDocument("fr", "v1_afptv_sport_broadcast_photos");
int jobPublicationID = 5;
// CMS Authentification
TestBehavior authenticateCMS = new ProductAuthenticationBehavior(cmsAuthClient);
ApplyTestBehaviorAction authenticateActionCMS = new ApplyTestBehaviorAction(runner, authenticateCMS);
authenticateActionCMS.doExecute(context);
// Document Creation
CreateVideoDocumentBehavior createDoc = new CreateVideoDocumentBehavior(cmsAuthClient, pdtIdentifier,
videoDocument);
ApplyTestBehaviorAction createDocAction = new ApplyTestBehaviorAction(runner, createDoc);
createDocAction.doExecute(context);
// get document data
videoDocument = createDoc.getVideoDocument();
G2VideoDocument g2VideoDocument = ((G2VideoDocument) G2ObjectFactory.parse(videoDocument));
g2VideoDocument.getProducts();
String linkToVideoDocument = g2VideoDocument.getLinkToSelf().toString();
String linkToProject = g2VideoDocument.getLinkToVideoProject().toString();
String projectID = IrisStringTools.extractIdFromUri(linkToProject);
String documentID = IrisStringTools.extractIdFromUri(linkToVideoDocument);
String etag = g2VideoDocument.getEditorialTag();
// Lock document Metadata
EditVideoDocumentMetaBehavior lockDocMeta = new EditVideoDocumentMetaBehavior(cmsAuthClient, pdtIdentifier,
videoDocument, documentID);
ApplyTestBehaviorAction lockDocMetaAction = new ApplyTestBehaviorAction(runner, lockDocMeta);
lockDocMetaAction.doExecute(context);
}
}
I run this in eclipse as JUnit test.
I thought about using a super class but it didn't work.
public class ProductDocumentValidationScenarioIT extends NoProductDocumentValidationScenarioIT {
public String ProductDocumentValidation(#CitrusResource TestRunner runner, #CitrusResource TestContext context)
throws BadNewsMLG2Exception {
return something;
}
}
Test behaviors is the way to go here. I suggest to use something like this
CreateVideoDocumentBehavior createDoc = new CreateVideoDocumentBehavior(cmsAuthClient, pdtIdentifier,
videoDocument);
runner.applyBehavior(createDoc);
what we finally did is to create a behavior runner (a java class) where all the behaviors are instanciated and then in the scenario we call the behavior runner with the behavior constant corresponding to the behavior I need:
public class BehaviorRunner {
private void doExecute(TestRunner runner, TestContext context, TestBehavior testBehavior) {
ApplyTestBehaviorAction behaviorAction = new ApplyTestBehaviorAction(runner,testBehavior);
behaviorAction.doExecute(context);
}
public void execute( String behaviorLabel, #CitrusResource TestRunner runner, #CitrusResource TestContext context) {
try {
switch (behaviorLabel) {
case BehaviorConstants.CREATE_VIDEO_DOCUMENT :
CreateVideoDocumentBehavior createVideoDocumentBehavior = new CreateVideoDocumentBehavior(cmsAuthClient, pdtIdentifier, VideoDocument.setUpVideoDocument2(LanguageConstants.EN, "v1_afptv_sport_broadcast_photos"));
doExecute(runner, context, createVideoDocumentBehavior);
break;
case BehaviorConstants.MOVIEDRAFT :
MovieDraftDocumentBehavior movieDraftDocumentBehavior = new MovieDraftDocumentBehavior(cmsAuthClient, pdtIdentifier, 1, g2VideoDoc);
doExecute(runner, context, movieDraftDocumentBehavior);
break;
case BehaviorConstants.PUBLICATION_PROGRESSION_STATUS:
GetPublicationProgressionStatusBehavior publicationProgressionStatusBehavior = new GetPublicationProgressionStatusBehavior(vdmAuthClient, pdtIdentifier , g2VideoDoc);
doExecute(runner, context, publicationProgressionStatusBehavior);
break;
case BehaviorConstants.VALIDATE :
ValidateDocumentBehavior validateDocumentBehavior = new ValidateDocumentBehavior(cmsAuthClient, pdtIdentifier, g2VideoDoc);
doExecute(runner, context, validateDocumentBehavior);
break;
default:
break;
}
we ended up with a scenario like this:
#Test
#CitrusTest
public void NoProductDocumentValidation(#CitrusResource TestRunner runner, #CitrusResource TestContext context) throws BadNewsMLG2Exception {
slf4jLogger.info("Montage analysis scenario START");
// execute scenario
// Document Creation
behaviorRunner.execute(BehaviorConstants.CREATE_VIDEO_DOCUMENT, runner, context);
// Lock document Metadata
behaviorRunner.execute(BehaviorConstants.EDITOR, runner, context);
// Lock Document Binary
behaviorRunner.execute(BehaviorConstants.BINARY_EDITOR, runner, context);
That saved us a lot of code lines since we're using different combinations of behaviors in different scenarios.
I hope this would help someone!

Test Observable.FlatMap Mockito

I've been looking on internet but haven't found the solution if any (new on UnitTest and Mockito)
It's possible to test a method that return a call of a service and manipulate it's result before to return it? Example;
public Observable<Reports> getUserReports(Integer userId) {
return serviceClient
.getReports(userId)
.flatMap(serviceReports -> {
System.out.println("Testing inside flatMap"); <- never reach this line therefore duno if methods down here are invoked and work perfectly
final Observable<List<Report>> reports = getPendingReports(userId, serviceReports);
//More methods that modify/update information
return zip(observable1, observable2, observable3
(report1, report2, report3) -> {
updateReports(otherArguments, report1, report2, report3);
return serviceReports;
});
});
}
So far I've tried;
#Test
public void myTest(){
when(serviceClient
.getReports(anyInt()))
.thenReturn(Observable.just(reports));
Observable<Reports> result = mocketClass.getUserReports(userId)
}
Tryed with Spy and Mock but no luck so far. Any hint or help would be great.
To mock getReports() behavior you need to mock the serviceClient firstly and pass it into your service class.
Just as example:
#Test
public void myTest(){
// Given
final ServiceClient mockedServiceClient = Mockito.mock(ServiceClient.class);
when(mockedServiceClient
.getReports(anyInt()))
.thenReturn(Observable.just(reports));
// and create an instance of your class under testing with injected mocked service client.
final MyUserService userService = new MyUserService();
userService.setServiceClient(mockedServiceClient);
// When run a method under test
Observable<Reports> actualResult = userService.getUserReports(userId)
// Then
// actualResult to be verified.
}

What could cause a class implementing "ApplicationListener<ContextRefreshedEvent>" not to be notified of a "ContextRefreshedEvent"

I have a Spring application listener implementing ApplicationListener<ContextRefreshedEvent> as follows:
#Profile({ Profiles.DEFAULT, Profiles.CLOUD, Profiles.TEST, Profiles.DEV })
#Component
public class BootstrapLoaderListener implements ApplicationListener<ContextRefreshedEvent>, ResourceLoaderAware, Ordered {
private static final Logger log = Logger.getLogger(BootstrapLoaderListener.class);
#Override
public int getOrder() {
return HIGHEST_PRECEDENCE;
}
#Autowired
private DayToTimeSlotRepository dayToTimeSlotRepository;
#Autowired
private LanguageRepository languageRepository;
private ResourceLoader resourceLoader;
#Override
#Transactional
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
initApplication();
}
private void initApplication() {
if (dayToTimeSlotRepository.count() == 0) {
initDayToTimeSlots();
}
if (languageRepository.count() == 0) {
initLanguages();
}
}
private void initDayToTimeSlots() {
for (Day day : Day.values()) {
for (TimeSlot timeSlot : TimeSlot.values()) {
DayToTimeSlot dayToTimeSlot = new DayToTimeSlot();
dayToTimeSlot.setDay(day);
dayToTimeSlot.setTimeSlot(timeSlot);
dayToTimeSlot.setDisabled(isDayToTimeSlotDisabled(timeSlot, day));
dayToTimeSlotRepository.save(dayToTimeSlot);
}
}
}
...
I rely on this listener class to insert reference data that is not updated nor deleted and I have a number of Spring integration tests that use this class, one of which fails because the listener is not notified (initDayToTimeSlots is not invoked).
I am trying to pinpoint where the problem comes from by debugging the tests and I noticed that when I run the problematic test class on its own, the tests contained in the class pass (indicating that the listener is notified) but when I run all of my application test classes together, the listener is not notified causing the test to fail (indicating that some other test changes/dirties the context).
Here is the problematic test class:
#ActiveProfiles({ Profiles.TEST })
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { FullIntegrationTestConfiguration.class, BaseTestConfiguration.class })
public class RegularDayToTimeSlotsTest {
private static int NUMBER_OF_REGULAR_DAY_TO_TIME_SLOTS_IN_WEEK = 25;
#Before
public void setup() {
//org.hsqldb.util.DatabaseManagerSwing.main(new String[] { "--url", "jdbc:hsqldb:mem:bignibou", "--noexit" });
}
#Autowired
private AdvertisementService advertisementService;
#Test
public void shouldNotContainSaturdayNorSunday() {
Set<DayToTimeSlot> regularDayToTimeSlots = advertisementService.retrieveRegularDayToTimeSlots();
assertThat(regularDayToTimeSlots).onProperty("day").excludes(Day.SATURDAY, Day.SUNDAY);
assertThat(regularDayToTimeSlots).onProperty("day").contains(Day.MONDAY, Day.THUESDAY);
}
#Test
public void shouldNotContainEveningNorNighttime() {
Set<DayToTimeSlot> regularDayToTimeSlots = advertisementService.retrieveRegularDayToTimeSlots();
assertThat(regularDayToTimeSlots).onProperty("timeSlot").excludes(TimeSlot.EVENING, TimeSlot.NIGHTTIME);
assertThat(regularDayToTimeSlots).onProperty("timeSlot").contains(TimeSlot.MORNING, TimeSlot.LUNCHTIME);
}
#Test
public void shouldContainCorrectNumberOfDayToTimeSlots() {
Set<DayToTimeSlot> regularDayToTimeSlots = advertisementService.retrieveRegularDayToTimeSlots();
assertThat(regularDayToTimeSlots).hasSize(NUMBER_OF_REGULAR_DAY_TO_TIME_SLOTS_IN_WEEK);
}
}
I am puzzled to see that both the prepareRefresh() and finishRefresh() methods within AbstractApplicationContext.refresh method are indeed called but that my listener is not notified...
Has anyone got any clue?
P.S. I know I could use #DirtiesContext in order to get a fresh context and I also know it would be preferable not to rely on an application listener for my tests but I am very anxious to understand what is going wrong here. Hence this post.
edit 1: When I debug the problematic test class in isolation, I notice that the event source is of type GenericApplicationContext and as explained above the test passes OK because the listener is notified. However when all test classes are run together, the event source is, oddly enough, of type GenericWebApplicationContext and no listener is found here in SimpleApplicationEventMulticaster:
#Override
public void multicastEvent(final ApplicationEvent event) {
for (final ApplicationListener<?> listener : getApplicationListeners(event)) {
Executor executor = getTaskExecutor();
if (executor != null) {
executor.execute(new Runnable() {
#Override
public void run() {
invokeListener(listener, event);
}
});
}
else {
invokeListener(listener, event);
}
}
}
edit 2: my comments in edit 1 make me asks myself what is responsible for determining the uniqueness of context configuration...
For instance, I have only two test classes with the following context configuration:
#ContextConfiguration(classes = { FullIntegrationTestConfiguration.class, BaseTestConfiguration.class })
I guess they both will use the same cached context, won't they? Now can a third class use the same cached context even though it does not have exactly the same context configuration?
Why does my test get a GenericWebApplicationContext above?
my comments in edit 1 make me asks myself what is responsible for
determining the uniqueness of context configuration...
The elements that make up the context cache key are described in the Context caching section of the "Testing" chapter in the reference manual.
For instance, I have only two test classes with the following context
configuration:
#ContextConfiguration(classes = {
FullIntegrationTestConfiguration.class, BaseTestConfiguration.class })
I guess they both will use the same cached context, won't they?
If they declare only those two configuration classes in that exact order, then yes.
Now can a third class use the same cached context even though it does not
have exactly the same context configuration?
No.
Why does my test get a GenericWebApplicationContext above?
A GenericWebApplicationContext is only loaded if your test class (or one of its superclasses) is annotated with #WebAppConfiguration.
If you are experiencing behavior that contradicts this, then you have discovered a bug in which case we would appreciate it if you could produce a scaled down test project in the issue repository and create a corresponding JIRA issue against the "Spring Framework" and its "Test" component.
Thanks,
Sam (author of the Spring TestContext Framework)

Resources