MockMvc not working for DataIntegrityViolationException - spring

We Spring developers know that if one tries to delete an entity that has other associated entities, a DataIntegrityViolationException is thrown.
I wrote a delete method catching both EmptyResultDataAccessException and DataIntegrityViolationException exceptions, throwing custom service-level exceptions for each case:
#Service
public class CityService {
#Autowired
private CityRepository repository;
public void delete(Long id) {
try {
repository.deleteById(id); // returns 204
}
catch (EmptyResultDataAccessException e) {
throw new ResourceNotFoundException("Id not found " + id); // returns 404
}
catch (DataIntegrityViolationException e) {
throw new DatabaseException("Integrity violation"); // returns 400
}
}
}
I've set all up so the first scenario returns 204, the second scenario returns 404, and the third scenario returns 400. Everything is working fine when I test it on Postman.
However, when I try to write an integrated test using MockMvc, the DataIntegrityViolationException scenario doesn't work! (the other two scenarios work).
#SpringBootTest
#AutoConfigureMockMvc
#Transactional
public class CityControllerIT {
#Autowired
private MockMvc mockMvc;
(...)
#Test
public void deleteShouldReturnBadRequestEventWhenDependentId() throws Exception {
mockMvc.perform(delete("/cities/{id}", 1L))
.andExpect(status().isBadRequest());
}
}
It's returning 204 instead of 400! I have printed some messages inside the try block and I have found that it is really not throwing an exception. The try block executes entirely, as there was no integrity violation.
#Service
public class CityService {
#Autowired
private CityRepository repository;
public void delete(Long id) {
try {
System.out.println("START");
repository.deleteById(id);
System.out.println("FINISH");
}
(...)
I am missing something about MockMvc fundamentals? Why integrity violation is being ignored when executing that MockMvc test?
I've saved a minimum-H2-just-clone-and-run project on Github:
https://github.com/acenelio/mockmvc-dataintegrity

DataIntegrityViolationException does NOT work properly with #Transactional, even in a common service method like:
#Service
public class MyService {
#Autowired
private MyRepository repository;
#Transactional
public void delete(Long id) {
try {
repository.deleteById(id);
}
catch (DataIntegrityViolationException e) {
// do something
}
}
}
Similarly, if you want to automate test a DataIntegrityViolationException scenario, you should NOT annotate your test class with #Transactional.
So if your writing transactional integrated tests (which rollback database for each test), you may want to create another test class without #Transactional annotation to test your DataIntegrityViolationException scenario.

Related

Spring cloud stream : how to use #Transactional with new Consumer<> functional programming model

I have StreamListener which I would like to replace using the new functional model and Consumer <>. Unfortunately, I don't know how to transfer #Transactional to new model:
#Transactional
#StreamListener(PaymentChannels.PENDING_PAYMENTS_INPUT)
public void executePayments(PendingPaymentEvent event) throws Exception {
paymentsService.triggerInvoicePayment(event.getInvoiceId());
}
I have tired certain things. Sample code below. I added logging messages to a different queue for tests. Then I throw an exception to trigger a rollback. Unfortunately, messages are queued even though they are not there until the method is completed (I tested this using brakepoints). It seems that the transaction was automatically committed despite the error.
#Transactional
#RequiredArgsConstructor
#Component
public class functionalPayment implements Consumer<PendingPaymentEvent> {
private final PaymentsService paymentsService;
private final StreamBridge streamBridge;
public void accept(PendingPaymentEvent event) {
paymentsService.triggerInvoicePayment(event.getInvoiceId());
streamBridge.send("log-out-0",event);
throw new RuntimeException("Test exception to rollback message from log-out-0");
}
}
Configuration:
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.queue-name-group-only=true
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.declare-exchange=true
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.bind-queue=true
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.transacted=true
spring.cloud.stream.source=log
spring.cloud.stream.bindings.log-out-0.content-type=application/json
spring.cloud.stream.bindings.log-out-0.destination=log_a
spring.cloud.stream.bindings.log-out-0.group=log_a
spring.cloud.stream.rabbit.bindings.log-out-0.producer.declare-exchange=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.bind-queue=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.queue-name-group-only=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.binding-routing-key=log
spring.cloud.stream.rabbit.bindings.log-out-0.producer.transacted=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.exchange-type=direct
spring.cloud.stream.rabbit.bindings.log-out-0.producer.routing-key-expression='log'
Have you tried something along the lines of
#Transactional
public class ExecutePaymentConsumer implements Consumer<PendingPaymentEvent> {
public void accept(PendingPaymentEvent event) {
paymentsService.triggerInvoicePayment(event.getInvoiceId());
}
}
. . .
#Bean
public ExecutePaymentConsumer executePayments() {
return new ExecutePaymentConsumer();
}

How to test exceptions handling in #ControllerAdvice

I currently have two ControllerAdvice in my application, I'm supposed to merge them into one.
But I need to test them before and after the merge, test the exception and the object that the controller return me.
I'm trying to make a jUnit test with Mockito but it seems impossible to test the exceptions without any context, without a controller, etc ...
Does anyone know how can I proceed to achieve what I'm trying to do ?
I also try to throw manually an exception but obviously it wasn't catched by the ControllerAdvice.
So basically here is what i'm trying to do:
Manually throw an exception
This exception is handled by my ControllerAdvice
Check the returned object (code & message)
Here is a sample of code I have:
#Before
public void setup() {
...
mockMvc = MockMvcBuilders.standaloneSetup(getController())
.setControllerAdvice(new GlobalControllerExceptionHandler())
.setCustomArgumentResolvers(resolver, resolver_0, resolver_1)
.setHandlerExceptionResolvers(exceptionResolver).build();
}
#Controller
#RequestMapping("/tests")
public static class RestProcessingExceptionThrowingController {
#RequestMapping(value = "/exception", method = GET)
public #ResponseBody String find() {
throw new EntityNotFoundException();
}
}
#Test
public void testHandleException() throws Exception {
mockMvc.perform(get("/tests/exception"))
.andExpect(new ResultMatcher() {
#Override
public void match(MvcResult result) throws Exception {
result.getResponse().getContentAsString().contains("global_error_test");
}
})
.andExpect(status().isNotFound());
}
I have the good status code at the end but it doesn't use my ControllerAdvice (I try with the debugger)
You can just call handler method directly
#ControllerAdvice
MyAdvice{
#ExceptionHandeler(listOfExxcetpions)
public ResponseEntity someOfMyExceptionsHandler(Exception e){
.....
}
}
and in test
MuTest{
private MyAdvice advice=new MyAdvice();
#Test
public void oneOfTests(){
Exception e=new SomeSortOfExceptionToTest();
resp=advice.someOfMyExceptionsHandler(e)
assertThat(resp).....dostuff;
}
}
If you want to test how spring integrates with your handlers - if your annotations are correct, ordering serialization etc - well that will be an integration test and you have to boot up test context - then you can throw exceptions directly from controller methods.

How to test same authorization logic in many rest controllers

So I have many rest controllers and I would like to write some reusable test approach for authorization
#RestController //1
public class PolicyController {
#PreAuthorize("#securityService.hasAccess(#policy)")
#GetMapping("policy/{policy}/group")
ResponseEntity subgrups(String policy) {
// impl
}
#PreAuthorize("#securityService.hasAccess(#policy)")
#GetMapping("policy/{policy}/participants")
ResponseEntity participants(String policy) {
// impl
}
}
#RestController//2
public class GroupController {
#PreAuthorize("#securityService.hasAccess(#policy)")
#GetMapping("policy/{policy}/group/{group}"
ResponseEntity subgroups(String policy, String group) {
// impl
}
}
#RestController //...n
When we follow good practice we should write test for every line of code, so probably I should write exactly so many duplicated tests as amount of controller * amount of methods so it would be a huge amount of such duplicated code
#WebMvcTest(controllers = PolicyController.class)
public class PolicyControllerTest {
//...mock all controller dependencies
#Autowired
private MockMvc mockMvc;
#MockBean
private PolicySecurity policyApi;
#Autowired
private SecurityService securityService;
#Test
public void whenSearchingForGroupAndHasAccessToPolicy() throws Exception {
when(policyApi.isActive(any())).thenReturn(false);
mockMvc.perform(get("/policies/{policy}/group", "123")
.contentType("application/json"))
.andExpect(status().isOk());
}
#Test
public void whenSearchingForGroupAndHasntAccessToPolicy() throws Exception {
when(policyApi.isActive(any())).thenReturn(true);
mockMvc.perform(get("/policies/{policy}/group", "123")
.contentType("application/json"))
.andExpect(status().isForbidden());
}
#Test
public void whenSearchingForParticipantsAndHasAccessToPolicy() throws Exception {
when(policyApi.isActive(any())).thenReturn(false);
mockMvc.perform(get("/policies/{policy}/participants", "123")
.contentType("application/json"))
.andExpect(status().isOk());
}
#Test
public void whenSearchingForParticipantsAndHasntAccessToPolicy() throws Exception {
when(policyApi.isActive(any())).thenReturn(true);
mockMvc.perform(get("/policies/{policy}/participants", "123")
.contentType("application/json"))
.andExpect(status().isForbidden());
}
}
this is only one controller with two methods, just imagine how many code there will be for 5 controllers and 30 endpoints, any idea how to write it in more maintainable way ?
Few pointers on what can be done. You can group your APIs based on the security configuration and then maintain a list of APIs for that group. This way you can just call one write one method for each authorization configuration where you will iterate through each API and run the mockMvc perform API method.
for(api in api_list) {
mockMvc.perform(get(api, "123")
.contentType("application/json"))
.andExpect(status().isForbidden());
}
If the security configuration changes for any one API in future, then the test case for the whole group will fail. Only problem with this approach is that maintaining the test cases will be difficult over time because you would need to keep updating the API lists everytime you change the authorization.
Note that it is always better to write isolated test cases for each API method to test only that unit.

Call original method on a #Spy then throw an exception

I have a #Transactional method that I must test when the transaction fails after it is called, for example :
#Service
public class MyService {
#Transactional
public void myMethod() {
// [...] some code I must run in my test, and throw an exception after it has been called but before the transaction is commited in order for the transaction to be rolled back
}
}
Here is the test class:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MyApp.class)
public class MyServiceTest {
#SpyBean
private MyService myService;
#Test
public void testMyMethod() {
doAnswer(/* some code which would call the real method, then throw an exception in order to cancel the transaction */)
.when(myService).myMethod();
// [...] other code that test other services when that service failed in the transaction but after its real method has been correctly executed
}
}
Could you tell me what code to put in the /* ... */ part in my test?
You can simply use the invocation.callRealMethod() then throw some exception inside the doAnswer:
#Test
public void testMyMethod() {
doAnswer(invocation -> {
invocation.callRealMethod();
throw new IllegalStateException();
})
.when(myService).myMethod();
// [...] other code that test other services when that service failed in the transaction but after its real method has been correctly executed
}

Why won't the transaction start in my junit test cases?

I have a Spring 3.1 MVC + Hibernate 3.6 project with its junit4 test suit. My problem is that there is no transaction starting in my test cases, even thought I added a #Transactional.
My test case calls a controller and a dao. In the controller, a transaction is started anyway, so it does not complain. In the dao, I added a #Transactional(propagation = Propagation.MANDATORY) to be sure it will take the test's transaction. And currently it raises an IllegalTransactionStateException, which I guess it means there is no current transaction.
I tried to create programmaticaly an transaction and it does work, which means the AOP proxy to get the dao service is not the cause of the problem. However I want to create a transaction with the #Transactional annotation.
here's my dao:
// ...imports...
#Repository("taskDao")
#Transactional(propagation = Propagation.MANDATORY)
public class TaskHome implements TaskDao {
private static final Log log = LogFactory.getLog(TaskHome.class);
private SessionFactory sessionFactory;
#Autowired
public TaskHome(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public Task findById(int id) {
log.debug("getting Task instance with id: " + id);
try {
Task instance = (Task) this.sessionFactory.getCurrentSession().get(
Task.class, id); // exception raised here!
if (instance == null) {
log.debug("get successful, no instance found");
} else {
log.debug("get successful, instance found");
}
return instance;
} catch (RuntimeException re) {
log.error("get failed", re);
throw re;
}
}
...
}
Here's my test case:
// ...imports...
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({ "/test-config.xml", "/applicationContext.xml" })
#TransactionConfiguration(defaultRollback = true)
#Transactional
public class TestTaskController {
private static ClassPathXmlApplicationContext context;
private static TaskDao taskDao;
#BeforeClass
public static void initHibernate() throws Exception {
context = new ClassPathXmlApplicationContext("applicationContext.xml");
taskDao = context.getBean("taskDao", TaskDao.class);
}
#Test
public void testOnSubmit() {
// expects an existing default transaction here
Task task = taskDao.findById(1); // fails already here
// ... calls the controller and does some tests.
}
}
I searched in all Spring's documentation and googled it in any way I could imagine, but I don't see why the transaction is not started.
Any help is very welcome.
When using #RunWith(SpringJUnit4ClassRunner.class) you should obtain beans from the application context created by SpringJUnit4ClassRunner rather than from your own one.
In your case things go wrong because #Transactional on the unit test creates a transaction in the application context managed by SpringJUnit4ClassRunner, but you call methods on the beans obtained from the application context created manually.
So, remove your #BeforeClass method and obtain TaskDao via autowiring:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({ "/test-config.xml", "/applicationContext.xml" })
#TransactionConfiguration(defaultRollback = true)
#Transactional
public class TestTaskController {
#Autowired
private TaskDao taskDao;
...
}
See also:
9.3.5 Spring TestContext Framework

Resources