Catching application context error spring junit - java

I am writing junits for my spring boot based application, and my beans depends on some configuration parameters specified in application-.properties.
In my configuration class where I am generating beans,
#Configuration
public class AppConfig{
#Value("${MyProperty}")
private String myProperty;
#Bean
public myBean bean1() throws MyException{
if(myProperty.contentEquals("abc"){
throw MyException("Value abc not allowed for bean1");
}
}
In my junit I want to detect this scenario , if in my junit I set the profile and run it errors out saying 'Application startup failed' and not reach my before method or test method.
How do I handle this so that the junit does not fail and I am able to detect the myexception as well.
Basically what I need is the application context creation to fail, but my unit tests to pass.
Thanks !

beans depends on some configuration parameters specified in application-.properties
This can be controlled by the #ConditionalOnProperty annotation directly on your bean definition. Therefore instead of throwing an exception in your test bean, you won't have it registered at all provided your config is not there:
I am writing junits for my spring boot based application, and my beans depends on some configuration parameters specified in application-.properties. In my configuration class where I am generating beans,
#ConditionalOnProperty(name = "myProperty", havingValue = "'hiThere'")
#Bean
public myBean bean1() {
//..
}
If you want to invoke some SpEL operation on the property you can use #ConditionalOnExpression:
#ConditionalOnExpression("${myProperty}.contains('ere')")
#Bean
public myBean bean1() {
//..
}

Related

Trouble executing a unit test that should ignore Spring annotations on the unit under test

I'm trying to execute a unit test for a service class that has an #Async("asyncExecutor") annotated method. This is a plain JUnit test class with no Spring runners and no intention of using Spring at all in the unit test. I get the exception,
BeanFactory must be set on AnnotationAsyncExecutionAspect to access qualified executor 'asyncExecutor'
Where asyncExectuor is the name of the bean to be used during normal execution. My configuration class looks like this and I solved that previous error message at runtime by adding the mode = AdviceMode.ASPECTJ portion. This service works at runtime without issue in an Async way.
#Configuration
#EnableAsync(mode = AdviceMode.ASPECTJ)
public class AsyncConfiguration {
#Bean(name = "asyncExecutor")
public Executor asyncExecutor() {
...
}
}
I don't understand why the Spring context is being constructed at all in the unit test. The test class is simply annotated #Test on the methods with no class annotations and no mention of Spring. I was hoping to unit test this service class method as a regular method ignoring the async nature, but the annotation is being processed for some reason.
I'm contributing to a much larger gradle + Spring 4 project that I'm not fully knowledgeable about. Is there anything I should be looking for to see if a Spring context is being created by default for all tests?
As you noticed, Spring context is not loaded, that is the reason of your error. Try to initialize Spring context in your test by adding #RunWith and #ContextConfiguration annotations

How do I unit-test the controller of a Spring Boot application with a DataSource dependency in a #ComponentScan #Configuration

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!"));
}
}

How can I use ActiveProfiles with Spring Boot tests with WebMvcTest?

I have a unit test that is testing a RestController using #WebMvcTest. The the controller class autowires a service class that I would like to mock. I found that I can use #Profile and #Configuration to create a config class for specifying primary beans to use when a profile is active. I tried adding an active profile to my unit test class, but it says it failed to load the ApplicationContext. I'm not sure how I can do that while using #WebMvcTest.
#ActiveProfiles("mockServices")
#RunWith(SpringRunner.class)
#WebMvcTest(VoteController.class)
public class VoteControllerTest {
...
It seems I may be approaching this wrong. Any help is appreciated.
Edit:
Here is my configuration class:
#Profile("mockService")
#Configuration
public class NotificationServiceTestConfiguration {
#Bean
#Primary
public VotingService getVotingService() {
return Mockito.mock(VotingService.class);
}
}
The error I'm actually getting is:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'VotingService'
I was able to solve it by using #MockBean for VotingService in my Unit Test class. However, I want to use the Profile configuration I have in NotificationServiceTestConfiguration without having to call out mock beans.
Any thoughts?

Junit / Spring Boot mocking classes

I've generated a Spring Boot web application using Spring Initializr, using embedded Tomcat + Thymeleaf template engine, and package as an executable JAR file.
Technologies used :
Spring Boot 1.4.2.RELEASE, Spring 4.3.4.RELEASE, Thymeleaf 2.1.5.RELEASE, Tomcat Embed 8.5.6, Maven 3, Java 8
I have this class
I have this junit test class:
#ContextConfiguration(classes={TestPersistenceConfig.class})
#RunWith(SpringRunner.class)
#SpringBootTest
public class JdbcRemoteUnitRepositoryTests {
#Autowired
private JdbcRemoteUnitRepository repository;
#Autowired
#InjectMocks
private SmsConfig smsConfig;
#Autowired
#InjectMocks
private NextelSMSSender smsService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void testGetAllUnits() throws DataAccessException, SQLException {
Assert.assertTrue(true);
}
}
the class NextelSMSSender:
#Service("smsService")
public class NextelSMSSender {
public final static String OK_STATUS_CODE = "200";
#Autowired
SmsConfig smsConfig;
..
}
.
#Configuration
#PropertySource("sms-gateway.properties")
public class SmsConfig {
#Value("${sms.domainId}")
private String domainId;
#Value("${sms.gateway.url}")
private String gatewayUrl;
..
}
But it seems that is not mocking the objects property, because when I package the app. I got this error:
***************************
APPLICATION FAILED TO START
***************************
Description:
Field smsConfig in com.plats.bruts.NextelSMSSender required a bean of type 'com.plats.bruts.config.SmsConfig' that could not be found.
Action:
Consider defining a bean of type 'com.plats.bruts.config.SmsConfig' in your configuration.
I created a mock bean named TestSmsConfig, tried to add #Bean, but I got a compilation error:
The annotation #Bean is disallowed for this
location
#Configuration
#Bean
public class TestSmsConfig {
#Value("fake.domainId")
private String domainId;
#Value("fake.sms.gateway.url")
private String gatewayUrl;
...
}
You're autowiring smsConfig, but you do not appear to provide that #Bean in your test application context.
In addition, you are using #InjectMocks incorrectly - this should be used to inject mock objects into the class under test (your NextelSMSSender class, not SmsConfig).
To resolve, add a #Bean method in your configuration class to provide SmsConfig bean instances.
Replace #InjectMocks from the SmsConfig variable in your test class with #MockBean. SmsConfig is an autowired mock object, and not the class under test.
See Section 41.3.4 - Of the Spring Boot Testing Features for more info:
Spring Boot includes a #MockBean annotation that can be used to define
a Mockito mock for a bean inside your ApplicationContext. You can use
the annotation to add new beans, or replace a single existing bean
definition. The annotation can be used directly on test classes, on
fields within your test, or on #Configuration classes and fields. When
used on a field, the instance of the created mock will also be
injected. Mock beans are automatically reset after each test method.
OK, so this is your configuration:
#Configuration
#PropertySource("sms-gateway.properties")
public class SmsConfig {
#Value("${sms.domainId}")
private String domainId;
#Value("${sms.gateway.url}")
private String gatewayUrl;
..
}
If you want to use that configuration in your test, you should add it to your configuration classes...
#ContextConfiguration(classes={TestPersistenceConfig.class, SmsConfig.class})
And not add it as #Autowired or whatever.

Access different Implementation/Component with one of same bean in spring boot

#Configuration
#Profile("live")
public class LiveProfileConfig{
#Bean
public BaseInterface commonInterface(){
return new LiveComponent();
}
}
#Configuration
#Profile("test")
public class TestProfileConfig{
#Bean
public BaseInterface commonInterface(){
return new TestComponent();
}
}
Now, in Main Spring boot application, I set the "live" profile as following
#ContextConfiguration(classes = {LiveProfileConfig.class, TestProfileConfig.class},loader=AnnotationConfigContextLoader.class)
#ActiveProfiles(value="live")
Then call one of implmentation to use component according active profile,
#Service
public class AnotherImpl implements AnotherInterface {
#Autowired
BaseInterface commonInterface;
}
Now, I want to access one of component methods (i.e. LiveComponent / TestComponent) based on active profile.
It works in JUnit test but while I try to run it with Spring Boot Application it gives me below error.
required a single bean, but 2 were found, Consider marking one of the
beans as #Primary, updating the consumer to accept multiple beans, or
using #Qualifier to identify the bean that should be consumed
Any help?

Categories

Resources