I have seen multiple threads about it. However, no one really solves my problem.
I have a Spring Boot 2.3 application with the three traditional layers: Controller, Service and DAO. The transactions are declared in my Service layer.
I would like to test my Controller layer using MockMvc, and I want the transactions to rollback at the end of the tests so that they all remain independent. However, I don't want the tests to give the Controller classes an access to the transactional context in order to have the same configuration as in runtime.
I created the following class:
#SpringBootTest
#AutoConfigureMockMvc
public class ApiIT {
#Autowired
private MockMvc mvc;
#Test
void restEndpointTest() {
...
This configuration doesn't rollback the transactions at the end of the tests.
When I annotate the class with #Transactional, they rollback, but the Controller classes can access the transactional context.
Related
I have an integration test on a endpoint of creating a user with its related entities. It turns out that the related entities were not persisted with the user entity.
However, it is working fine when running the normal spring boot application. Is it possible to achieve this during testing?
This is the log when running the integration test of the endpoint
And this is the log when calling from Postman to the normal application
as you can notice the roles are not inserted to the rel_mi_user__mi_user_role table during integration testing.
The setup source code for the integration test is shown below
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
private MockMvc mockMvc;
#Autowired
private MiMiUserRepository miMiUserRepository;
private static Logger log = LoggerFactory.getLogger(MiMiUserResourceIT.class);
#Test
#Transactional
void testRegisterBackOfficeUser() throws Exception {
MiMiUserRegistrationDTO userRegistrationDTO = new MiMiUserRegistrationDTO();
userRegistrationDTO.setPassword("test12345");
userRegistrationDTO.setEmail("john#gmail.com");
userRegistrationDTO.setContactNo("0188991122");
userRegistrationDTO.setUserRoles(List.of("BACKOFFICE"));
MiMiUserProfileRegistrationDTO profile = new MiMiUserProfileRegistrationDTO();
profile.setSalutation("Mr");
profile.setFirstName("John");
profile.setLastName("Lee");
userRegistrationDTO.setProfile(profile);
mockMvc
.perform(
post("/v1/p/user/register/back-office")
.contentType(MediaType.APPLICATION_JSON)
.content(TestUtil.convertObjectToJsonBytes(userRegistrationDTO))
)
.andExpect(status().isCreated());
MiUser u = miMiUserRepository.findOneWithMiUserRolesByEmailIgnoreCase(userRegistrationDTO.getEmail()).get();
assertThat(u.getUserStatus()).isEqualTo(UserStatus.NEW);
}
I guess it has to do with the fact that your whole test is annotated with #Transactional. When you annotate your test method with #Transactional, everything that happens during that test happens within one single transaction. As you might know, changes are only flushed to the database at the end of a transaction unless you call the flush method manually which is generally discouraged unless you know what you are doing because you interfere with your JPA provider's flushing optimization.
Only when the changes are flushed to the DB, auto-generated properties like an ID is set on your entity and DB constraints are evaluated. Yes, you're reading it right, you might even violate a DB constraint within your test without the test failing!
Here you can find more reasons why you should not annotate your tests with #Transactional.
If you need to save several entities in the arrange part of your test, you can autowire the Spring TransactionTemplate and create all your entities within a manual transaction. The same might apply to asserts where you load an entity from the DB and want to evaluate a lazy-loaded collection of said entity. As far as I can tell, neither of said scenarios apply to your test, just try omitting the #Transactional and your test should work.
Side note: if you write #DataJpaTests you always need to call the saveAndFlush method of the repository instead of the save method because #DataJpaTests are always transactional.
I have a Spring 5 web application which does validations only on service level DTOs. That is to say, the incoming request is not validated in controller, but the DTO going into a service layer method is validated on that level. This is a requirement because this is how this is done on other applications and other people want to keep it like that for consistency.
I have tests that use Spring MockMvc to test the controller methods. I'd like to test the error response on validation errors but when I run my tests, bean validation is not done. It works fine when I build and deploy the app, though. Validation also works for tests if I put the validation annotations on the class representing the incoming request and add #Valid to the controller method argument corresponding to the request. It just doesn't work when its done on the service layer.
So my question is is this even supposed to work with MockMvc, that is to say, should validation occur normally when the controller calls the service method? Or is there some configuration I need to have in place for this to work during tests? I doubt it since validation works if I try adding it to controller level...
Here is my test class setup:
#ContextConfiguration(classes = {MyTestConfig.class})
#WebAppConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
public class MyTest {
private MockMvc mvc;
#Autowired
private WebApplicationContext wac;
#Before
public void setup() throws Exception {
this.mvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
The MyTestConfig class just has #EnableWebMvc annotation, base packages for scanning and some unrelated bean definitions.
My DTO has #NotNull annotation on a property, the service method has a #Validated annotation on the class and a #Valid annotation on the method parameter which is the DTO.
With these settings, when I make a request in a test to the controller endpoint using the MockMvc instance mvc above, validation does not occur on the service method.
So is this even supposed to work?
what is the usage of #DataJpaTest annotation when testing ?
do we need it with every db connecting test?
the test is working even without the annotation
#RunWith(SpringRunner.class)
#DataJpaTest
why #DataJpaTest is used why it has used and when to use it?
By default, #DataJpaTest will configure an in-memory embedded database, scan for #Entity classes and configure Spring Data JPA repositories. It is also transactional and rollback at the end of each test. If we wanna disable transaction management, we can use:
#Transactional(propagation = Propagation.NOT_SUPPORTED)
We can also inject a TestEntityManager bean specifically designed for tests which is an alternative to the JPA EntityManager.
#Autowired
private TestEntityManager entityManager;
Hence, our Test Class should be similar to:
#RunWith(SpringRunner.class)
#DataJpaTest
public class MyJPAUnitTest extends SpringJpaUnitTestApplicationTests {
#Autowired
private TestEntityManager entityManager;
#Autowired
CustomerRepository repository;
#Test
public void testExample() {...}
}
More details here: https://grokonez.com/testing/datajpatest-with-spring-boot
Use #DataJpaTest annotation
allows you to test domain logic so you can check JPA mappings and you can check queries
It also configures Hibernate, Spring Data and an in-memory database.
You can override the in-memory database if you want to reuse a real one.
It also provides access to a TestEntityManager bean. This is an alternative to the regular entity manager that just provides some
convenience methods that you often want to use in your tests
I'm using redis caching and spring boot annotations[#Cacheable and #CahePut],
I made RedisManager transactionAware, which will use the outer transaction[callee of caching layer]
#Bean
public RedisCacheManager cacheManager() {
RedisCacheManager rcm =
RedisCacheManager.builder(redisConnectionFactory())
.cacheDefaults(cacheConfiguration())
.transactionAware()
.build();
return rcm;
}
while testing as below, I'm using embedded redis-:
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureTestDatabase
#Transactional
public class RoleServiceImplTest extends TestingProfile {
#Before
public void setup() throws Exception {
//setup server and services
redisServer = new RedisServer(redisPort);
redisServer.start();
}
#Test
public void getUsersForRoleForTemplateRole() {
// call to caching layer methods directly annotated with #Cachable
}
...
Both times [ with and without #Transactional ] spring calls cache.put(key,result) without exception but it only persists values in case of without #Transactional.
Couldn't find much on internet, kudos to any help in advance.
In short just put #Commit or Rollback(false) annotation over your class or test method.
Spring by default rollback every Transaction after the test method.
https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#testcontext-tx
In the TestContext framework, transactions are managed by the TransactionalTestExecutionListener, which is configured by default, even if you do not explicitly declare #TestExecutionListeners on your test class. To enable support for transactions, however, you must configure a PlatformTransactionManager bean in the ApplicationContext that is loaded with #ContextConfiguration semantics (further details are provided later). In addition, you must declare Spring’s #Transactional annotation either at the class or the method level for your tests.
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/context/transaction/TransactionalTestExecutionListener.html
Declarative Rollback and Commit Behavior
By default, test transactions will be automatically rolled back after completion of the test; however, transactional commit and rollback behavior can be configured declaratively via the #Commit and #Rollback annotations at the class level and at the method level.
We have spring services in our project. for example UserService.
These services use the #Transactional annotation.
We just tried to execute junit tests that involves these services and for some reason the #Transactional annotations are ignored. (For example, i use a #Test without #Transactional , it goes to a service that is indeed anotated #Transactional, in that service it saves a new object(entity) the entity is being saved in the DB before the end of the transaction (Commit).)
I would like to point out that we don't want our junit tests to be annotated with #Transactional themselves.
Is it possible to enforce junit to not ignore the #Transactional annotations in our services?
Thanks,
You should explicitly tell JUnit to use transactions (example from reference):
#ContextConfiguration(locations={"example/test-context.xml"})
#TransactionConfiguration(transactionManager="txMgr", defaultRollback=false)
public class CustomConfiguredTransactionalTests {
// class body...
}
Then you can use #NonTransactional/#Rollback/etc annotations in order to control the behavior for particular methods.
Manual configuration is required as previously mentioned, as transactions are not persisted in Spring test contexts.
Spring reference for this topic can be found here