I want to test my spring application. It requires authentication, so I create an user object and persist it in #Before method. But i can not do authentication because, as i think, init() method is executed in another session.
IntegrationTest class:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
#Transactional
public class IntegrationTest {
#PersistenceContext
private EntityManager entityManager;
#Autowired
private PasswordEncoder passwordEncoder;
#LocalServerPort
int port;
private String URL;
#Before
public void init() {
User user = new User();
user.setUsername("testUser");
user.setPassword(passwordEncoder.encode("test"));
user.setEmail("test#test.com");
user.setEnabled(true);
entityManager.persist(user);
entityManager.flush();
entityManager.clear();
RestAssured.port = port;
URL = "http://localhost:" + String.valueOf(port) + "/users/user";
}
#Test
public void givenNotAuthenticatedUser_whenLoggingIn_thenCorrect() {
final RequestSpecification request = RestAssured.given().auth().basic("testUser", "test");
request.when().get(URL).then().assertThat().statusCode(200);
}
}
But if I use my userRepository and call
userRepository.save(user);
instead of
entityManager.persist(user);
entityManager.flush();
entityManager.clear();
everything works fine. I also have to remove #Transactional annotation.
Firstly I thought it was because, there was no commit - I saw that there is no changes in the user table. How to force EntityManager to commit data?
How to use EntityManager in test? And why UserRepository does work well?
As stated in the documentation:
By default, the framework will create and roll back a transaction for each test.
You can override it using #Commit on your test class and put TestTransaction.end() after entityManager.clear().
Now the test works ok but I still can not understand why and how UserRepository works without commiting the transaction.
Related
I'm currently facing an issue with testing RabbitMQ consumers with mocks. The issue seems to be that one test class runs with an application context without any mocks, as expected. The next test class to run sets up some mocks that it expects the consumers to use, however when the test runs and a message is sent and it gets picked up by the non-mocked consumers from the application context created for the first test class. As a result my second test fails.
Here is the first test:
#SpringBootTest
public class DemoApplicationTests extends AbstractTestNGSpringContextTests {
#Autowired
private RabbitAdmin rabbitAdmin;
private Logger logger = LoggerFactory.getLogger(this.getClass());
#Test(priority = 1)
public void contextLoads() {
logger.info("=============== CONSUMERS: " + rabbitAdmin.getQueueProperties(USER_MESSAGING_QUEUE).get(RabbitAdmin.QUEUE_CONSUMER_COUNT));
}
}
Second test:
#SpringBootTest
public class UserServiceTests extends AbstractTestNGSpringContextTests {
#Autowired
private UserService userService;
#Autowired
private UserMessageConsumer userMessageConsumer;
#MockBean
#Autowired
private ThirdPartyUserDataClient thirdPartyUserDataClient;
#Autowired
private UserRepository userRepository;
#Autowired
private RabbitAdmin rabbitAdmin;
#Test(priority = 2)
public void createUpdateUserTest() {
logger.info("=============== CONSUMERS: " + rabbitAdmin.getQueueProperties(USER_MESSAGING_QUEUE).get(RabbitAdmin.QUEUE_CONSUMER_COUNT));
String additionalData = org.apache.commons.lang3.RandomStringUtils.random(5);
Mockito.when(thirdPartyUserDataClient.getAdditionalUserData(ArgumentMatchers.anyLong())).thenReturn(additionalData);
User user = new User();
user.setName("Test User");
user.setState(UserState.PENDING);
user = userService.createUser(user);
Assert.assertNotNull(user.getId());
User finalUser = user;
Awaitility.await().until(() -> {
User user2 = userService.getUserById(finalUser.getId());
return finalUser != null && additionalData.equals(user2.getAdditionalData());
});
user.setState(UserState.CREATED);
user = userService.updateUser(user);
Assert.assertEquals(UserState.CREATED, user.getState());
}
}
The consumer:
#Component
public class UserMessageConsumer {
private Logger logger = LoggerFactory.getLogger(this.getClass());
public static final String FAILED_TO_GET_ADDITIONAL_DATA = "FAILED_TO_GET_ADDITIONAL_DATA";
#Autowired
private UserService userService;
#Autowired
private ThirdPartyUserDataClient thirdPartyUserDataClient;
public void handleUserCreatedMessage(UserCreatedMessage userCreatedMessage) {
Long userId = userCreatedMessage.getUserId();
User user = userService.getUserById(userId);
if (user != null) {
String additionalData;
try {
additionalData = thirdPartyUserDataClient.getAdditionalUserData(userId);
logger.info("Successfully retrieved additional data [{}] for user [{}].", additionalData, userId);
} catch (HttpClientErrorException ex) {
additionalData = FAILED_TO_GET_ADDITIONAL_DATA;
logger.warn("Failed to retrieve additional data for user [{}].", userId, ex);
}
user.setAdditionalData(additionalData);
userService.updateUser(user);
}
}
}
This brings up two related questions:
How am I supposed to properly do mock bean testing with consumers in
Spring?
It looks like Spring is bringing up a new a
ApplicationContext for each test class, indicated by the consumer count increasing on the subsequent test runs. It appears
that #MockBean affects the cache key of the ApplicationContext (see:
Mocking and Spying Beans in Spring Boot) and likely explains why there are multiple application contexts.
But how do I stop the consumers in the other stale application contexts from
consuming my test messages?
I've bugjar'd this issue here: RabbitMQ MockBean BugJar
Add #DirtiesContext to each test class to shut down the cached context.
I'm trying to write the unit test case for the below file.
RoleDataController.Java
#RestController
#RequestMapping("/updateRoleData")
public class RoleDataController extends ControllerBase {
#Autowired
public EntityManager entityManager;
public Session session = entityManager.unwrap(Session.class);
RoleData _roleData = new RoleData();
#RequestMapping("/getRoleData")
public String findRoleData(){
List roleList =_roleData.findRoleData(session,123456);
return JsonHelper.toJson(roleList);
}
}
RoleDataControllerTest.java
public class RoleDataControllerTest {
RoleData _roleData = new RoleData();
#Autowired
public EntityManager entityManager;
public Session session = entityManager.unwrap(Session.class);
#Test
public void findRoleData() throws Exception {
List roleList =_roleData.findRoleData(session, 123456);
Assert.assertNotNull(roleList);
}
}
I'm getting NullPointerException in the below line
public Session session = entityManager.unwrap(Session.class);
Please help to fix this.
Unit tests don't start the Spring context and so the #Autowired annotation won't work - no dependency injection will be done by Spring. You either want to write an integration test or mock the behaviour of other beans.
#ContextConfiguration(classes = { ServiceConfig.class,PersistenceConfiguration.class, MailConfig.class })
#Transactional
public class CreateStatsIT extends AbstractTestNGSpringContextTests {
#Autowired
private UserRepository userRepository;
#Test
#Rollback
#Transactional
public void insertUserIT() {
User u = new User(1L, "Test");
u = userRepository.save(s);
List<User> us = userRepository.findAll();
System.out.println(us.size());
}
}
I would expect that the user would not be present in the database after the completion of the test but it is. The Spring Integration Tests documentation describes that the tests rollback automatically and also that #Rollback does not let the transaction be committed.
What am I doing wrong?
I have 2 DataSources in my app.
So, to get the required JdbcTemplate, i use #Qualifier. But, when i do like below, the test runs... but stays waiting indefinitely, if there is any use of JdbcTemplate in the "Method Under Test".
#Service
#Transactional
public class SampleDatabaseService {
#Autowired
#Qualifier("firstDbJdbcTemplate")
private JdbcTemplate firstDbJdbcTemplate;
#Autowired
#Qualifier("secondDbJdbcTemplate")
private JdbcTemplate secondDbJdbcTemplate;
#Cacheable("status")
public Map<String, Device> readAllValidDeviceStatus() {
Map<String, Device> allDeviceStatuses = new HashMap<>();
//Stops at below line indefinitely if "SpyBean" is used
List<StatusDetail> statusDetails = firstDbJdbcTemplate
.query(SqlQueries.READ_DEVICE_STATUS, BeanPropertyRowMapper.newInstance(StatusDetail.class));
statusDetails
.stream()
.filter(deviceStatus -> deviceStatus.getName() != "Some Invalid Name")
.forEach(deviceStatus -> allDeviceStatuses
.put(deviceStatus.getName(), buildDevice(deviceStatus)));
return allDeviceStatuses;
}
/** More Stuff **/
}
and the Test :
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#Transactional
#Rollback
#ActiveProfiles("test")
public class SampleDatabaseServiceTest {
#SpyBean
#Qualifier("firstDbJdbcTemplate")
private JdbcTemplate firstDbJdbcTemplate;
#Autowired
private SampleDatabaseService serviceUnderTest;
#Before
public void populateTables() {
//Insert some Dummy Records in "InMemory HSQL DB" using firstDbJdbcTemplate
}
#Test
public void testReadAllValidDeviceStatus() {
// When
Map<String, Device> allDeviceStatuses = serviceUnderTest.readAllValidDeviceStatus();
// Then
assertThat(allDeviceStatuses).isNotNull().isNotEmpty();
// More checks
}
/* More Tests */
}
But, when i replace the #SpyBean with #Autowired in Test, it works fine.
Why is it so? Any help is greatly appreciated. :)
Use it in below format
#MockBean(name = "firstDbJdbcTemplate")
private JdbcTemplate firstDbJdbcTemplate;
i have a Problem understanding the #Transactional and #TransactionConfiguration(defaultRollback = true) Annotations.
I have a test Code which inserts a userAccount into the Database, then another account with the same name should be inserted, which should result in a DataIntegrityViolationException because the AccountName is marked as Unique. This works fine if #Transactional and #TransactionConfiguration(defaultRollback = true) is not sepcified at the TestClass level. But if Rollback is enabled i don't get the Exception because even in the same method the Data is not inserted into the databasse. If i set a breakpoint after inserting the first Account, the Database is still empty.
Here is my Code:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:/spring/applicationContext.xml"})
#Transactional
#TransactionConfiguration(defaultRollback = true)
public class DaoTests {
#Autowired
private Repository repo;
#Autowired
private AccountService userService;
#Before
public void orgInstAccount() {
Organization o = new Organization();
o.setName("Organisation 1");
repo.saveEntity(o);
Institution i1 = new Institution();
i1.setName("xyz");
i1.setOwningOrganization(o);
repo.saveEntity(i1);
}
#Test(expected = DataIntegrityViolationException.class)
public void saveUserFail() {
Account user = new Account();
user.setAccountname("chz");
user.setPassword(userService.calcMD5Hash("123"));
user.setOwningInstitution(repo.getInstitutionByName("xyz"));
repo.saveEntity(user);
Assert.assertNotNull(repo.getAccountByName("chz"));
Account userNew = new Account();
userNew.setAccountname("chz");
userNew.setPassword(userService.calcMD5Hash("123"));
userNew.setOwningInstitution(repo.getInstitutionByName("xyz"));
repo.saveEntity(userNew);
//Here the Exception should be thrown but everything works fine.
}
}
The Repository Implementation is:
#Repository
#Transactional
#SuppressWarnings("unchecked")
public class RepositoryHibernateImpl implements Repository {
#Autowired
private SessionFactory factory;
#Override
public void saveEntity(Entity hce) {
factory.getCurrentSession().save(hce);
}
}
Maybe the Problem is because the Repository and the TestClass are marked with #Transactional?
Thank you in Advance.
Call flush() which will try to call sql immediatly instead of deferring it till transaction boundary
factory.getCurrentSession().flush()