I am using Spring's #Transactional in my DAO's service class. The application has beans configured in XML(s) and via annotations like #Service.
My test case configuration looks like below:
#RunWith(SpringJunit4ClassRunner.class)
#ContextHierarchy({
#ContextConfiguration(locations = {"classpath:spring/spring.xml"}),
#ContextConfiguration(classes = {Service.class})
})
#ComponentScan(...)
public class TestRunner {
#Autowired
private Service service;
#Test
public void testSave() {
service.save(...);
}
}
I have below configured in XML:
<tx:annotation-driven transaction-manager="txmManager"/>
<!-- bean for hibernate5 transaction manager using sessionfactory -->
The service class looks as below:
#Service
public class Service {
#Autowired
private DAO dao;
#Transactional
public Entity save(Entity entity) {
dao.save(entity);
}
}
Now it all works perfectly during source context run but when I run the test cases, the transaction is never invoked. Note: there is no NPE on calling Service.class's method.
I tweaked around and noticed that if I create a bean for Service.class in my (test) spring xml file, the test cases work as expected, i.e., the transaction manager is called and the in-memory database is updated.
I expected SpringJunit4ClassRunner to create a MergedContext and the transaction-manager configuration to be automatically kicked in upon Service#save call.
Any thoughts on what am I missing here?
Hibernate - 5.2.6.Final,
Spring - 4.2.0.RELEASE,
Spring-test - 4.1.6.RELEASE
Related
Consider the following basic Spring Boot application:
#SpringBootApplication
#ComponentScan(basePackages = "webmvctestproblem.foo")
public class Application {
public static void main(String[] args) {
run(Application.class, args);
}
}
It contains only two other beans. One controller:
#RestController
class Greeter {
#GetMapping("/")
String greet() {
return "Hello, world!";
}
}
And one configuration in webmvctestproblem.foo containing a DataSource dependency:
#Configuration
class Bar {
#Autowired
private DataSource dataSource;
}
Running the application normally (through gradlew bootrun, e.g.) succeeds. Thus, confirming that the app is configured correctly under normal conditions.
However, running the following test causes a runtime error because Spring still attempts to resolve the data source bean dependency on the configuration class:
#RunWith(SpringRunner.class)
#WebMvcTest
public class GreeterTest {
#Test
public void test() throws Exception {
}
}
Of course, there isn't one to resolve because the test is a #WebMvcTest that is designed to create only MVC-related beans.
How do I get rid of the error? I have already tried excluding the configuration class using the excludeFilters attribute of the existing #WebMvcTest annotation and a new #ComponentScan annotation on the test itself. I don't want to resort to turning it into an integration test with #SpringBootTest.
(The project is also available on GitHub for convenience.)
If the DataSource is not mandatory for the test run, simply mock the DataSource with #MockBean in the test class.
#RunWith(SpringRunner.class)
#WebMvcTest
public class GreeterTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private DataSource dataSource;
#Test
public void shouldGreet() throws Exception {
mockMvc
.perform(get("/"))
.andExpect(content().string("Hello, world!"));
}
}
Spring will automatically create a Mock for DataSource and inject it into the running test application context.
Based on your source code it works.
(Btw: Your source code has a minor issue. The Greeter controller class is in the base package but the component scan only scans on the "foo" package. So there will be no Greeter controller on the test run if this isn't fixed.)
#WebMvcTest creates a "slice" of all the beans relevant to WebMvc Testing (Controllers, Json conversion related stuff and so forth).
You can examine the defaults in org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTypeExcludeFilter
In order to find which beans are actually supposed to be Run Spring must resolve them somehow, right?
So spring test tries to understand what should be loaded and what not by passing through these filters.
Now, if you mark anything with #Configuration spring "knows" that this is the place where the place should be found. So it will load the configuration and then will check which beans defined in this configuration must actually be loaded. However the object of configuration itself must be loaded anyway.
Now the process of loading the configuration object includes injecting stuff into these configurations - this is lifecycle of object creation of spring.
And this is a source of mistake here:
#Configuration
class Bar {
#Autowired
private DataSource dataSource;
}
Spring loads Bar and tries as a part of loading this object to autowire the data source. This fails since the DataSource Bean itself is excluded by filters.
Now in terms of solution:
First of all, why do you need this DataSource to be autowired in the Configuration object? Probably you have the bean that uses it, lets call it "MyDao", otherwise I don't see a point of such a construction, since #Configuration-s are basically a place to define bean and you shouldn't put business logic there (if you do - ask a separate question and me/our colleagues will try to help and suggest better implementation).
So I assume you have something like this:
public class MyDao {
private final DataSource dataSource;
public MyDao(DataSource dataSource) {
this.dataSource = dataSource;
}
}
#Configuration
class Bar {
#Autowired
private DataSource dataSource;
#Bean
public MyDao myDao() {
return new MyDao(dataSource);
}
}
In this case however you can rewrite the configuration in a different way:
#Configuration
class Bar {
// Note, that now there is no autowired datasource and I inject the parameter in the bean instead - so that the DataSource will be required only if Spring will have to create that MyDao bean (which it won't obviously)
#Bean
public MyDao myDao(DataSource dataSource) {
return new MyDao(dataSource);
}
}
Now the Bar object will still be created - as I've explained above, but it beans including MyDao of course won't be created, problem solved!
The solution with #Autowired(required=false) provided by #Anish B. should also work - spring will attempt to autowire but won't fail because the data source is unavailable, however you should think whether its an appropriate way to deal with this issue, your decision...
Before you can #Autowire the DataSource bean you need to define the DataSource in some config class or in the properties file. Something like this
spring.datasource.url = jdbc:mysql://localhost/abc
spring.datasource.name=testme
spring.datasource.username=xxxx
spring.datasource.password=xxxx
spring.datasource.driver-class-name= com.mysql.jdbc.Driver
spring.jpa.database=mysql
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
Or
#Configuration
public class JpaConfig {
#Bean
public DataSource getDataSource()
{
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.driverClassName("org.h2.Driver");
dataSourceBuilder.url("jdbc:h2:file:C:/temp/test");
dataSourceBuilder.username("sa");
dataSourceBuilder.password("");
return dataSourceBuilder.build();
}
You should use
#AutoConfigureMockMvc
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#RunWith(SpringRunner.class)
on your test class, then you can inject
#Autowired
private MockMvc mockMvc;
and use it in test
mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andReturn();
i suggest you read the documentation about testing. You can test a spring boot application in 100s of different ways.
WebMvcTest
as suggested by the documentation try, defining what controller class that you want to test in the #WebMvcTest annotation.
#RunWith(SpringRunner.class)
#WebMvcTest(Greeter.class)
public class GreeterTest {
#Autowired
private MockMvc mockMvc;
#Test
public void shouldGreet() throws Exception {
mockMvc
.perform(get("/"))
.andExpect(content().string("Hello, world!"));
}
}
I've used a separated bean configuration file for my beans instead of using spring-boot annotation like("component", "Service" or, ...) in the application:
#SpringBootTest
#ContextConfiguration(loader = AnnotationConfigContextLoader.class)
class CheckActiveWorkerTest {
#Autowired
UserJpaRepository userJpaRepository;
#Test
void execute_AddUser_exception() {
userJpaRepository.get(....);
.....
}
}
The UserJpaRepository is an interface and it has #Repository.
When I run this test, the userJpaRepository will be null and the spring boot test runner could not find its bean. Eventually, this test raises a NullPointerException!
How can I use the JpaRepositories during the tests?
How to instantiate seesion before unit testing execution so that autowired session in service class creates bean correctly ?
My test uses some methods from a service class. This service class method uses an autowired session. The point is that i don't know how to create/inject into the session in the test (or before it) so that the session bean creates correctly in the service with the details i set beforehand.
The session class looks like this:
#Component
#Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserSession {
Long idUser;
//...other
//geters and seters
}
Test class:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = {Application.class})
public class MyTestClass{
#Autowired
private UserSession session;
#Autowired
private MyServiceClass myServiceClass;
#Test
public void myTestMethod() {
....
//This is where i wanted to set some session detailes
//Something like this:
session.setUserRolls(...);
myServiceClass.myServiceMethod();
}
}
In my service class i have something like this:
#Service
public class MyServiceClass{
#Autowired
private UserSession session;
private void myServiceMethod(){
....
List <UserRol> rolls = session.getUserRolls();
//in this case i want to retrieve user rolls from session object
//
//now i get an error in my current implementation that looks like this
//Error: no Scope registered for scope name "session"
}
}
The only thing you should do is to put #WebAppConfiguration before your test class:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = {Application.class})
#WebAppConfiguration
public class MyTestClass{
......
}
Inside the class you should inject your session class using #autowired exactly like you did.
After that inside your test, before you call your service function, you can set your session details using setters. The details will be visible inside your service so everything should work just fine.
The presence of #WebAppConfiguration on your test class instructs the TestContext framework (TCF) that a WebApplicationContext (WAC) should be loaded for your tests, so that will give you session scoped beans.
IMHO trying to autowire a session scoped bean in a unit test is bad design. Normal unit testing should be possible by mocking the bean. With mock framework like mockito, you can even control that methods have been called with the expected parameters.
If you need actual interaction with that bean - but it is now an integration test - you should manually build the bean the way it should look like in the use case you are testing, and manually inject it into the service class you are testing.
Anyway, having dependencies on a session scoped bean (isn't it controller of view layer?) inside a service bean (should be service layer) smells. It may be a perfectly correct design, but you really should think twice about that, mainly if it is now hard to test.
Don't forget: test should remain as simple as possible. If they cannot, look again at your general design.
You can use the Spring MockMVC framework and add a #WebAppConfiguration, that will give you session scoped beans.
Look at this example How to test a spring controller method by using MockMvc?
Are there any difference between enabling transaction using #Transactional annotation on test class and enabling transaction with test listener #TestExecutionListeners(TransactionalTestExecutionListener.class)?
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("/applicationContext.xml")
#Transactional
public class MyTestClass {
....
}
and
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("/applicationContext.xml")
#TestExecutionListeners(TransactionalTestExecutionListener.class)
public class MyTestClass {
....
}
TransactionalTestExecutionListener is the only guy who creates transactions. It checks #Transactional annotation presence and then opens transaction. In case on test execution listeners are specified spring uses some default test execution listeners one of which is TransactionalTestExecutionListener, that is why it seems that listener and annotation works separately.
Suppose I have this:
#Transactional(rollbackFor = NotificationException.class)
public interface PersonManagerService {
public void addPerson(Person person);
}
and an implementation:
public class PersonManagerServiceImpl implements PersonManagerService {
public OtherService otherService;
public void addPerson(Person person) {
// stuff
}
// getter and setter for otherService
}
How would I go about mocking the otherService dependency while still having the addPerson method hit the database?
My scenario is that I want to test that a particular exception causes a rollback of the saving of the person that is being added. This exception would come from the OtherService class, which I don't want to call the real version of. I am currently using Spring Transaction annotations so I have a test like this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {various locations})
public class Test() {
#Autowired
PersonManagerService service;
#Test
public void test() {
// how to call setOtherService so it can be a mock?
}
If I try to cast the auto wired bean, I get an IllegalArgumentException because it is a proxy. If I made the Impl not use an interface, I could use CGLIB but I don't want to do that. If I create the impl programmatically, then it's not tied into the transaction flow. What other options do I have?
There are various ways you can tackle this program (from best to worst):
take advantage of #Profiles - in Spring 3.1 you can associate profile name with every bean. When you are starting an application context you provide active profiles and only beans without any profile associated or with profile matching provided will be instantiated. This is a very powerful mechanism.
#Profile("prd")
public class PersonManagerServiceImpl implements PersonManagerService
//...
#Profile("test")
public class PersonManagerServiceMock implements PersonManagerService
//...
#ContextConfiguration
#ActiveProfiles(value = "test")
public class Test {
use primary or #Primary - if you are autowiring otherService in PersonManagerServiceImpl you can define a second mock bean with primary="true" attribute or #Primary annotation. Spring will prefer primary beans when autowiring.
unwrap transactional proxy (see: Is it possible to unproxy a Spring bean? and Mocking a property of a CGLIB proxied service not working) to access setter. A bit hacky, but works for others