SpringBoot 2 + Junit5: null with #Value - spring-boot

I have an application with SpringBoot2 and Junit5, and now I'm trying to make a test. I have a this class called OrderService that looks like this:
#Component
public class OrderService {
#Value("#{'${food.requires.box}'.split(',')}")
private List<String> foodRequiresBox;
#Value("#{'${properties.prioritization}'.split(',')}")
private List<String> prioritizationProperties;
#Value("${further.distance}")
private Integer slotMeterRange;
#Value("${slot.meters.long}")
private Double slotMetersLong;
As you can see, the class has many #Value annotations that extracts values from application.properties file.
In the POM file I have these dependences:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<version>2.0.5.RELEASE</version>
</dependency>
Tn the test/resources folder I have the application.properties file with this information:
properties.prioritization:vip,food
food.requires.box:pizza,cake,flamingo
further.distance:2
slot.meters.long:0.5
The test file looks like this:
#ExtendWith(SpringExtension.class)
#TestPropertySource(locations="classpath:application.properties")
public class OrderServiceTest {
OrderService orderService;
#BeforeEach
void before(){
orderService = new OrderService();
}
#Test
void findAll() {
Order order = new Order().withDescription("2x Pizza with Salad\\n2x Kebab with Fries\\n1x Hot dog with Fries\\n2x Pizza with Fries");
assertTrue(orderService.orderHasFood.test(order));
}
}
But the test throws NullPointerException when it tries to use foodRequiresBox, so there is a problem to read the application.properties file.
Could you tell how can I read the application.properties file for the tests?

1st Solution
I would recommend to use Spring's internal annotation called #SpringJUnitConfig
This annotation is actually the same as #ExtendWith(SpringExtension.class) BUT you can configure your spring application contexts for your test in the same way you used to use #ContextConfiguration.
Or if you want a full Spring Boot Test you could just combine:
#SpringJUnitConfig
#SpringBootTest
public class OrderServiceTest {
...
}
2nd Solution
Another way is to not use Spring at all, but mock all the internal stuff with e.g. Mockito and write a plain simple Unit Test.
You could then set your normally via Spring injected annotated #Value fields via org.springframework.test.util.ReflectionTestUtils.

I'd recommend using org.springframework.test.util.ReflectionTestUtils (as indicated in #mrkernelpanic second solution) using the setField() method, in order to avoid initializing the full Spring context.
Here is a snippet:
// ReflexionTestUtils.setField(targetObject, "targetFieldName", valueToInject);
ReflexionTestUtils.setField(orderService, "foodRequiresBox", "pizza,cake,flamingo");

Related

Using Spring StateMachine in an EJB container

I'm trying to use Spring's state machine library inside of a JavaEE application running in a container to manage instances of a class named RunInfo. But I'm not sure how to integrate the two together.
I tried adding the following dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.2.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-core</artifactId>
<version>2.4.1</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.7.2</version>
</dependency>
on spring-boot-autoconfigure and then adding the following configuration class:
#Configuration
#EnableAutoConfiguration
#ComponentScan(basePackages = {"com.ourdomain"})
public class StateConfiguration {}
I then wrote a simple state machine configuration:
#Configuration
#EnableStateMachine(name = "runInfoStateMachine")
public class RunInfoStateMachineConfiguration extends EnumStateMachineConfigurerAdapter<RunInfoState, RunInfoEvent> {
and state handler:
#Component
public class RunInfoStateHandler extends LifecycleObjectSupport {
// ...
#Autowired private StateMachine<RunInfoState, RunInfoEvent> stateMachine;
}
based on another project that I manage (which is a pure Spring Boot app).
The state handler is created, but the state machine reference is never injected; i.e., stateMachine in the above code is always null.
How does one integrate the Spring StateMachine framework into an EJB application?

NoClassDeffFoundError: org/springframework/data/jpa/repository/Repository

I am using JpaRepository, here is my code
public Interface EmpRepository extends JpaRepository<Employee, Integer> {}
class EmployeeServicImpl {
private EmpRepository empRepository;
#Autowired
EmployeeServicImpl (EmpRepository theRepository) {
this.empRepository = theRepository;
}
}
added below dependencies in my pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
While start the application, resolution of declared constructor of bean from class loader : NoClassDeffFoundError : /org/springframework/data/jpa/repository/Repository
Some points,
Keep your repository interface in a separate package.
Use #Repository on your repository.
Use #Service or #Component annotation at EmployeeServicImpl class.
It appears as when you're starting the application, Spring is trying to find EmpRepository dependency to instantiate your service but is not able to find the repository since it is not declared as a #Repository by you.
Further, reason for using #Service is so that EmployeeServicImpl becomes available to Spring too.

Cucumber Spring DI two Step classes loading Spring context

I'm using cucumber-spring to create my integration tests and use DI provided by Spring.
The way it's done: there is a separate project that defines all the necessary cucumber dependencies:
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>${cucumber.version}</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-spring</artifactId>
<version>${cucumber.version}</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit</artifactId>
<version>${cucumber.version}</version>
</dependency>
The project also has a base test class annotated with SpringBootTest (that allows to load application context):
#RunWith(SpringRunner.class)
#SpringBootTest(
classes = {SomeBeans.class}
)
public class BaseTestStateSteps {
I'm adding this project as a dependency in the pom of my project:
<dependency>
<groupId>com.externalproject</groupId>
<artifactId>spring-test-cucumber</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
Then I have my runner class:
#RunWith(Cucumber.class)
#CucumberOptions(
plugin = {"pretty", "junit:target/cucumber/cucumber.xml", "json:target/cucumber/cucumber.json",
"html:target/cucumber/html/"},
tags = {"#Smoke"},
features = {"src/test/resources/features"},
glue = {"com.externalproject.steps","com.mycompany.steps"}
)
public class RunTestIT {
}
And I also have my Step class:
public class MyTestSteps {
#Autowired
ClassToAutowire classToAutowire;
#Given("^I can do something$")
public void generateRandomPhoneNumber() {
classToAutowirer.doSomething();
}
So the issue I'm running:
- if I don't have my RunTestIT annotated with SpringBootTest or with ContextConfiguration - ClassToAutowire is null (it's not autowired);
- if I addone of those annotations I'm running into error:
cucumber.runtime.CucumberException: Glue class class com.externalproject.steps.BaseTestStateSteps and class com.mycompany.steps.MyTestSteps both attempt to configure the spring context. Please ensure only one glue class configures the spring context
I need the functionality of Steps from external project (so I don't have to copy/paste from there and get the latest feature updates), but I also need dependencies from my spring context to be autowired.
I read the article https://thepracticaldeveloper.com/2018/03/31/cucumber-tests-spring-boot-dependency-injection/
But it only deals with multiple step definitions inside the same project.
Do you have any ideas how I can solve the issue?

How to make both validation annotations in a ConfigurationProperties bean and a #FeignClient interface work together?

Let's say I have this application.yml (which will be environment-dependent e.g. via Spring profiles):
app.remote:
url: http://whatever.url.it.is:8080/
and matching Java-style configuration properties class:
#Configuration
#ConfigurationProperties("app.remote")
public class MyRemoteProperties {
#NotBlank
private String url;
// matching getter/setter...
}
I want some kind of client for my remote url:
#Service
#FeignClient(value = "remote", url = "${app.remote.url}")
public interface MyRemote {
#GetMapping("/what/ever/rest/api")
String stuff();
}
Unfortunately I can't get the validation work for MyRemoteProperties e.g. when the app.remote.url property is blank (empty) the application doesn't start (Spring fails at wiring the MyRemote bean) and I get this error:
Caused by: java.lang.IllegalStateException: No Feign Client for
loadBalancing defined. Did you forget to include
spring-cloud-starter-netflix-ribbon?
(and I don't want load-balancing; I assume this is because the URL is empty at some point, then it expects some load-balancer config hence Ribbon here in the error message).
Or maybe I don't known how to plug it into the MyRemote interface's configuration, e.g. I also tried:
#FeignClient(value = "remote", configuration = MyRemoteProperties.class)
But same result.
How do I get this validation thing to work?
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
At some point where the interface is called:
#Service
public RandomServiceOrController {
#Autowired
private MyRemote myRemote;
public void processMyStuff() {
// ...
String myStuff = myRemote.stuff();
// ...
}
}
Don't forget the #Validated annotation on your Java properties class:
#Validated
#Configuration
#ConfigurationProperties("app.remote")
public class MyRemoteProperties {
#NotBlank
private String url;
// matching getter/setter...
}
Your application won't start because of the missing property, not because of a non-defined-loadbalancing-client-you-don't-need (thus making its error message more awkward).

Error creating bean with name 'gemfireCache': FactoryBean threw exception on object creation

I am trying to create an "employee" Region and put some data into it. But, I am getting Exception below:
[warn 2018/12/27 17:15:46.518 IST tid=0x1] Exception
encountered during context initialization - cancelling refresh
attempt: org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'gemfireConfiguration': Injection of
resource dependencies failed; nested exception is
org.springframework.beans.factory.BeanCreationException: Error
creating bean with name 'gemfireCache': FactoryBean threw exception on
object creation; nested exception is java.lang.NoClassDefFoundError:
it/unimi/dsi/fastutil/ints/Int2ObjectOpenHashMap
[warn 2018/12/27 17:15:46.519 IST tid=0x1] Invocation of
destroy method failed on bean with name 'gemfireCache':
org.apache.geode.cache.CacheClosedException: A cache has not yet been
created.
[error 2018/12/27 17:15:46.522 IST tid=0x1] Caught exception
while allowing TestExecutionListener
[org.springframework.test.context.web.ServletTestExecutionListener#c667f46]
to prepare test instance
[com.gemfire.demo.Gemfire1ApplicationTests#48bfb884]
Domain class
#Region("employee")
public class Employee {
#Id
public String name;
public double salary;
...
}
Repository class
#Repository
public interface EmployeeRepository extends CrudRepository<Employee, String> {
Employee findByName(String name);
}
Configuration class
#Configuration
#ComponentScan
#EnableGemfireRepositories(basePackages = "com.gemfire.demo")
public class GemfireConfiguration {
#Autowired
EmployeeRepository employeeRepository;
#Bean
Properties gemfireProperties() {
Properties gemfireProperties = new Properties();
gemfireProperties.setProperty("name", "SpringDataGemFireApplication");
gemfireProperties.setProperty("mcast-port", "0");
gemfireProperties.setProperty("log-level", "config");
return gemfireProperties;
}
#Bean
#Autowired
CacheFactoryBean gemfireCache() {
CacheFactoryBean gemfireCache = new CacheFactoryBean();
gemfireCache.setClose(true);
gemfireCache.setProperties(gemfireProperties());
return gemfireCache;
}
#Bean(name="employee")
#Autowired
LocalRegionFactoryBean<String, Employee> getEmployee(final GemFireCache cache) {
LocalRegionFactoryBean<String, Employee> employeeRegion = new LocalRegionFactoryBean<String, Employee>();
employeeRegion.setCache(cache);
employeeRegion.setClose(false);
employeeRegion.setName("employee");
employeeRegion.setPersistent(false);
employeeRegion.setDataPolicy(DataPolicy.PRELOADED);
return employeeRegion;
}
}
POM.XML
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-gemfire</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.9.0</version>
</dependency>
Adding additional tips with your above GemFire/Spring JavaConfig configuration class above.
Given you are using Spring Data Kay (implied by your use of the Spring Boot 2.0.x parent POM, i.e. org.springframework.boot:spring-boot-dependencies; see here), then you could be using Spring Data GemFire's (relatively) new and convenient Annotation-based configuration model.
By doing so, your GemfireConfiguration class above would become...
#PeerCacheApplication
#EnableGemfireRepositories(basePackages = "com.gemfire.demo")
class GemfireConfiguration {
#Bean(name="employee")
LocalRegionFactoryBean<String, Employee> getEmployee(GemFireCache cache) {
LocalRegionFactoryBean<String, Employee> employeeRegion =
new LocalRegionFactoryBean<String, Employee>();
employeeRegion.setCache(cache);
employeeRegion.setClose(false);
employeeRegion.setDataPolicy(DataPolicy.PRELOADED);
return employeeRegion;
}
}
A few things to keep in mind:
#PeerCacheApplication is meta-annotated with #Configuration so you do not need the explicit Spring #Configuration annotation on the configuration class.
#PeerCacheApplication allows you to adjust the GemFire log-level (along with other logging configuration) using the logLevel annotation attribute. Similarly, you can set the log-level using the corresponding property, spring.data.gemfire.cache.log-level in a Spring Boot application.properties file (see here). There are many other attributes and corresponding properties (e.g. name) you can use to adjust and customize other configuration.
While String-based package names are supported on #EnableGemfireRepositories and similar annotations, we generally prefer and recommend users to use the type-safe variant basePacakgeClasses. You only need to refer to a single type from each top-level package where your application Repositories are kept.
The explicit #Autowired annotation is not needed on your bean definitions. You do not need to explicit inject the EmployeeRepository in the configuration class to have it initialized; just inject it into the #Service class where it will be used.
For convenience, the name ("employee") of the Region bean definition on your LOCAL "employee" Region, will also be used as the name of the Region, so employeeRegion.setName("employee") is unnecessary.
You should not combine LocalRegionFactoryBean.setPersistent(:boolean) with LocalRegionFactoryBean.setDataPolicy(:DataPolicy) since the DataPolicy is going to take precedence.
While #ComponentScan is perfectly acceptable and even convenient in development, I generally do not prefer nor recommend users to use component-scanning. It is usually always better to be explicit.
As stated in the comments, you chould remove <relativePath/> from your parent definition in your application Maven POM file.
Final note, as of this post, Spring Boot 2.0.8.RELEASE is the latest release.
As for your classpath issues, if you are using Maven correctly, then Maven should take care of pulling in the correct transitive dependencies.
You can refer to the many examples I have in this repo for further clarification.
Hope this helps!
As mentioned in comments, the error shows some dependencies (java.lang.NoClassDefFoundError: it/unimi/dsi/fastutil/ints/Int2ObjectOpenHashMap) are missing. Please add corresponding dependencies in your pom.xml

Resources