SpringBoot: Testing the Service layer - spring

Let's assume that I have two classes:
TodoRepository
TodoService
the TodoRepository is a simple CRUD Repository:
public interface TodoRepository extends CrudRepository<T, ID> {
}
the TodoService is just a class which calls this Repository:
#Service
public class TodoService{
private final TodoRepository todoRepository;
#Autowired
public TodoService(TodoRepository todoRepository) {
this.todoRepository = todoRepository;
}
public void createTodo(Todo todo) {
todoRepository.save(todo);
}
}
Should I bother testing the service layer?
Edit:
Thanks to the explanation from #Dherik. I created a test class which looks like:
Note: I am using JUnit5, Mockito and Spring framework
#ExtendWith(SpringExtension.class)
class TodoServiceTest {
#MockBean
private TodoRepository todoRepository;
private TodoService todoService;
#BeforeEach
void setUp() {
todoService = new TodoService(todoRepository);
}
#AfterEach
void tearDown() {
clearInvocations(tanklevelRepository);
}
#Test
public void createTodo() {
todoService.createTodo(new Todo());
// verify if the save method is called when createTodo is called too
verify(todoRepository, times(1)).save(any(Todo.class));
}
}

Yes, it's important.
Even now being a very simple class, maybe some developer on the future could add some weird condition on this method createTodo that the Todo is not saved anymore.
If you write a test for the actual method to verify if the save is called, the developer will be advised about the situation if he makes some change that affect the Todo save.
See a pseudo test example:
#Test
public void createTodo() {
TodoRepository todoRepository = mock(TodoRepository.class);
TodoService todoService = new TodoService(todoRepository);
todoService.createTodo(new Todo());
// verify if the save method is called when createTodo is called too
verify(todoRepository, times(1)).save(any(Todo.class));
}

I've seen this kind of thing tested with Junit using a Mock framework and injecting a mock repo into the service, then checking the mock repo was called. That seems really pointless to me as the test knows too much about the implementation. If you change the implementation you have to rewrite the test, so it's no use for refactoring.
I would test this kind of thing with an integration test that treated the app like a black box. i.e. start the app, trigger whatever creates a todo and check that it was created. I'd probably use cucumber-jvm and have a scenario with a step to create a todo and another to retrieve it.
I think you should create tests that
help you write reliable code
allow refactoring without the need to rewrite tests
prove the application does what it's supposed to

Related

Replace Mockito's when thenReturn implementation that was previously defined with new implementation

I am adding tests to a large codebase that is similar to this:
public class MyTests{
#Mock
private DBService dbService;
#Before
public void init(){
Mockito.when(dbService.getFromDb).thenReturn(getReturnResult());
}
}
Where getReturnResults() gets the some fake data. In 99% of cases this is the implementation I want, but when testing for exceptions I would like to do something like this:
#Test
public void useDifferentDBResults(){
Mockito.when(dbService.getFromDb).thenReturn(getDifferentReturnResult());
...
}
Where getDifferentReturnResult() gets some different data that will result in an error. I just need to replace the implementation for this one test.
Two things come to my mind:
Introduce in the same test class a second instance of DBService and use it "...when testing for exceptions":
#Mock
private DBService dbService;
#Mock
private DBService dbService2;
#Before
public void init() {
Mockito.when(dbService.getFromDb).thenReturn(getReturnResult());
Mockito.when(dbService2.getFromDb).thenReturn(getDifferentReturnResult());
}
...
#Test
public void useDifferentDBResults(){
// Use dbService2 here
...
}
or
Write a separate test class just for testing for exception(s) and move to that class useDifferentDBResults() and the respective test method(s).
If you want to dynamically provide the result of the mock then you could use the Answer interface of mockito. See this post for more details:
Dynamic return values with Mockito
Nevertheless I think in tests it is a best practice to repeat yourself in order to make each testmethod more readable. So my advice would be to move the mock initialization to each test method.

how to conditionally not create beans in spring boot?

In my application, I have a component that reads data from other system when the application is started.
However, during testing, I don't want this component to be created
#Component
#Slf4j
public class DeviceStatisticsSyncHandler {
#EventListener
public void handle(ApplicationReadyEvent event) {
syncDeviceStatisticsDataSync();
}
#Value("${test.mode:false}")
public boolean serviceEnabled;
}
I can use condition to solve this, but other code readers need to understand, so I don't think this is a very good method:
#EventListener(condition = "#deviceStatisticsSyncHandler .isServiceEnabled()")
public void handle(ApplicationReadyEvent event) {
syncDeviceStatisticsDataSync();
}
public boolean isServiceEnabled() {
return !serviceEnabled;
}
#Value("${test.mode:false}")
public boolean serviceEnabled;
My application doesn't use Profiles, is there any other method to solve this problem.
Spring Boot version:2.1.3
One possible option is not to load the DeviceStaticsticsSyncHandler at all if you're in a test mode.
The "test.mode" is not a good name here, because the production code contains something tightly bound to the tests.
How about the following approach:
#Component
#ConditionalOnProperty(name ="device.stats.handler.enabled", havingValue = "true", matchIfMissing=true)
public class DeviceStatisticsSyncHandler {
// do whatever you need here, but there is no need for "test.mode" enabled related code here
}
Now in Tests you can define a test property "device.stats.handler.enabled=false" on the test itself or even place that definition in src/test/reources/application.properties so it will be false for all tests in the module.
An obvious advantage is that this definition is pretty much self explanatory and can be easy understood by other project maintainers.
for me, it's not the case of the condition rather environment-related. I will solve this problem using spring profile.
Step 1: Create an Interface first
public interface DeviceStatisticsSyncHandler {
public void handle(ApplicationReadyEvent event);
}
Step 2: Create an Implementation for production
#Component
#Profile("!test")
public class DeviceStatisticsSyncHandlerImpl implements DeviceStatisticsSyncHandler {
#EventListener
#Override
public void handle(ApplicationReadyEvent event) {
syncDeviceStatisticsDataSync();
}
}
step 3: create an implementation of test
#Component
#Profile("test")
public class DeviceStatisticsSyncHandlerTestImpl implements DeviceStatisticsSyncHandler {
#EventListener
#Override
public void handle(ApplicationReadyEvent event) {
//do Nothing
}
}
final step
All you need to do is set/toggle the property
-Dspring.profiles.active=test
or
-Dspring.profiles.active=prod
I found a way to achieve this without any further external configuration required.
The idea is to create a general configuration that applies to all integration tests and use #MockBean there to replace the real bean. So one should create a class like this under the test classpath (i.e. that is not scanned during normal application launch):
#Configuration
public class IntegrationTestConfiguration
{
#MockBean
public DeviceStatisticsSyncHandler deviceStatisticsSyncHandler;
}
I was actually surprised that #MockBean can be used here, but the Javadoc explicitly points that out: Can be used as a class level annotation or on fields in either #Configuration classes, or test classes that are #RunWith the SpringRunner..

How do I verify that an event has been published using Spring, JUnit, and Mockito?

I'm using Spring 4.3.8.RELEASE with JUnit 4.12 and Mockito 1.10.18. I have a service that publishes events ...
#Service("organizationService")
#Transactional
public class OrganizationServiceImpl implements OrganizationService, ApplicationEventPublisherAware
publisher.publishEvent(new ZincOrganizationEvent(id));
#Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher)
{
this.publisher = publisher;
}
...
#Override
public void save(Organization organization)
{
...
publisher.publishEvent(new ThirdPartyEvent(organization.getId()));
My question is, how do I verify in a JUnit test that an event has actually been published?
#Test
public void testUpdate()
{
m_orgSvc.save(org);
// Want to verify event publishing here
I prefer the opposite approach, which is more integration test-ey:
🧙‍♂️Mock the ApplicationListener using Mockito
đź”—Register the mock application listener onto the ConfigurableApplicationContext
🔨Do work
✔Verify the mock has received the event
With this approach you are testing that an event has been published by means that someone is receiving it.
Here is the code of a rudimental authentication test. Among other conditions, I test that a login event has occurred
#Test
public void testX509Authentication() throws Exception
{
ApplicationListener<UserLoginEvent> loginListener = mock(ApplicationListener.class);
configurableApplicationContext.addApplicationListener(loginListener);
getMockMvc().perform(get("/").with(x509(getDemoCrt())))//
.andExpect(status().is3xxRedirection())//
.andExpect(redirectedUrlPattern("/secure/**"));
getErrorCollector().checkSucceeds(() -> {
verify(loginListener, atLeastOnce()).onApplicationEvent(any(UserLoginEvent.class));
return null;
});
}
My advice is to unleash the power of Mockito to deeply verify the event arguments. In my case, I will extend my code to:
Check that the username in the login event matches the authenticated principal
Perform additional tests where the user blatantly fails to login and I will expect one of the various login failure events
If you want to test if you didn't forget to call publishEvent method inside your OrganizationServiceImpl you can use something like this:
class OrganizationServiceImplTest {
private OrganizationServiceImpl organizationService;
private ApplicationEventPublisher eventPublisher;
#Before
public void setUp() {
eventPublisher = mock(ApplicationEventPublisher.class);
organizationService = new OrganizationServiceImpl();
organizationService.setApplicationEventPublisher(eventPublisher)
}
#Test
public void testSave() {
/* ... */
organizationService.save(organization);
verify(eventPublisher).publishEvent(any(ThirdPartyEvent.class));
}
}
Test case above will verify whether or not there was an invocation of publishEvent method.
For more check the documentation.
Regarding:
My question is, how do I verify in a JUnit test that an event has actually been published?
You have to test ApplicationEventPublisher implementation and probably without mocks if you want to verify actual sending.

Unit Testing of Spring MVC Controller: Failing to fully perform request

I have a simple controller defined as below:
#Controller
#RequestMapping("/rest/tests")
public class TestController {
#Autowired
private ITestService testService;
#RequestMapping(value="/{id}", method=RequestMethod.DELETE)
#ResponseStatus(value = HttpStatus.OK)
public void delete(#PathVariable Integer id)
{
Test test = testService.getById(id);
testService.delete(test);
}
}
I have been trying to test the delete method, and have not succeeded so far. The test I have written is pretty simple too.
public class MockmvcTest {
#InjectMocks
private TestController test;
private MockMvc mockMvc;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(test).build();
}
#Test
public void myTest() throws Exception {
this.mockMvc.perform(delete("/rest/tests/{id}", new Integer(4)))
.andExpect(status().isOk()
}
}
I have tested the method using "advanced rest client" extension in chrome and it works as expected.
By working I mean that the entity with the given id is deleted from database.
When myTest() is executed the status code is still 200, but the entity is not removed from the database.
What might be the reason behind this behavior?
You're using Mockito to inject mock service beans into your TestController (in particular, ITestService). All mocks by definition have no behaviour until you specify it, by default all operations you perform will either do nothing or return null. You can easily confirm that by setting a breakpoint inside TestController.delete method, executing the test in debug mode and inspecting values of test and testService variables.
Mockito is used for unit-level tests that replace SUT's collaborators with a mock that you set up to behave in a certain verifiable way. Once you call a method on your SUT (in your case that's TestController) you can assert whether it adheres to its contract or not.
It's actually a big no-no to allow your automated tests to modify a real instance of a database.

How do I reset my database state after each unit test without making the whole test a transaction?

I'm using Spring 3.1.1.RELEASE, Hibernate 4.1.0.Final, JPA 2, JUnit 4.8.1, and HSQL 2.2.7. I want to run some JUnit tests on my service methods, and after each test, I would like any data written to the in-memory database to be rolled back. However, I do NOT want the entire test to be treated as a transaction. For example in this test
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({ "classpath:test-context.xml" })
public class ContractServiceTest
{
…
#Autowired
private ContractService m_contractService;
#Test
public void testUpdateContract()
{
// Add the contract
m_contractService.save(m_contract);
Assert.assertNotNull(m_contract.getId());
// Update the activation date by 6 months.
final Calendar activationDate = Calendar.getInstance();
activationDate.setTime(activationDate.getTime());
activationDate.add(Calendar.MONTH, 6);
m_contract.setActivationDate(activationDate.getTime());
m_contractService.save(m_contract);
final List<Contract> foundContracts = m_contractService.findContractByOppId(m_contract.getOpportunityId());
Assert.assertEquals(foundContracts.get(0), m_contract);
} // testUpdateContract
there are three calls to the service, ("m_contractService.save", "m_contractService.save", and "m_contractService.findContractByOppId") and each is treated as a transaction, which I want. But I don't know how to reset my in-memory database to its original state after each unit test.
Let me know if I need to provide additional information.
Since you are using Hibernate, you could use the property hibernate.hbm2ddl.auto to create the database on startup every time. You would also need to force the spring context to be reloaded after each test. You can do this with the #DirtiesContext annotation.
This might add a bit extra overhead to your tests, so the other solution is to just manually delete the data from each table.
#DirtiesContext was no solution for me because the whole application context gets destroyed an must be created after each test -> Took very long.
#Before was also not a good solution for me as I have to create #Before in each integration test.
So I decided to create an TestExecutionListener which recreates the database after each test. (With Liquibase, but it also works with Flyway and normal SQL)
public class CleanupDatabaseTestExecutionListener
extends AbstractTestExecutionListener {
public final int getOrder() {
return 2001;
}
private boolean alreadyCleared = false;
#Override
public void prepareTestInstance(TestContext testContext) throws Exception {
if (!alreadyCleared) {
cleanupDatabase(testContext);
alreadyCleared = true;
} else {
alreadyCleared = true;
}
}
#Override
public void afterTestClass(TestContext testContext) throws Exception {
cleanupDatabase(testContext);
}
private void cleanupDatabase(TestContext testContext) throws LiquibaseException {
ApplicationContext app = testContext.getApplicationContext();
SpringLiquibase springLiquibase = app.getBean(SpringLiquibase.class);
springLiquibase.setDropFirst(true);
springLiquibase.afterPropertiesSet(); //The database get recreated here
}
}
To use the TestExecutionListenere I created a custom test annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#RunWith(SpringRunner.class)
#SpringBootTest(classes = OurderApp.class)
#TestExecutionListeners(mergeMode =
TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS,
listeners = {CleanupDatabaseTestExecutionListener.class}
)
public #interface OurderTest {
}
Last but not least, I can now create tests and I can be sure that the database is in a clean mode.
#RunWith(SpringRunner.class)
#OurderTest
public class ProductSaveServiceIntTest {
}
EDIT: I improved my solution a bit. I had the problem that sometime one test method destroyed my database for all upcoming tests within the test class. So I created the annotation
package com.ourder.e2e.utils;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface ClearContext {
}
and added this to the CleanupDatabaseTestExectionListener.
#Override
public void afterTestMethod(TestContext testContext) throws Exception {
if(testContext.getTestMethod().getAnnotation(ClearContext.class)!=null){
cleanupDatabase(testContext);
}
super.afterTestMethod(testContext);
}
with help of these two snippets I am now able to create tests like this:
#Test
#ClearContext
public void testWhichDirtiesDatabase() {}
If you use flyway for migrations, I use the following pattern:
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class JUnit5Class {
#Autowired
Flyway flyway;
#BeforeAll
public void cleanUp(){
flyway.clean();
flyway.migrate();
}
}
#TestInstance allows you to make #BeforeAll non static and thus you can migrate only once per test class. If you want to reset it for each test remove the class anotation and make change #BeforeAll to #BeforeEach.
As of JUnit 5 you could also create a custom extension and access the data source from the Spring context, like so (using Kotlin):
class DatabaseCleanerExtension : AfterEachCallback {
override fun afterEach(context: ExtensionContext) {
val ds = SpringExtension.getApplicationContext(context).getBean(DataSource::class.java)
ds.connection.use { connection ->
connection.prepareStatement("DELETE FROM my_table").execute()
}
}
}
You can then register the extension as follows:
#SpringBootTest
#ExtendWith(DatabaseCleanerExtension::class)
class SpringJunitExtensionApplicationTests { .. }
Now after each test the callback is executed and you can easily annotate any test classes this applies to.
Here is also a video on settings this up.
Make a #Before method in which you delete all data from database. You are using Hibernate so you can use HQL: delete from Contract.
You can use #Transactional annotation at Junit class level from org.springframework.transaction.annotation.Transactional.
For example:
package org.test
import org.springframework.transaction.annotation.Transactional;
#Transactional
public class ArmyTest{
}
I solve the same problem using a random memory database for each test:
#DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
#TestPropertySource(properties = {
"spring.datasource.url=jdbc:hsqldb:mem:${random.uuid}"
})
I was having same issue and solved following this
http://www.javafixing.com/2021/10/fixed-how-to-cleanup-h2-db-after-each.html?m=1
basically you can clean the DB after every test method with:
#Sql(scripts = "clean_file.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
on clean_file.sql you can add all the SQL statements to reset the db

Resources