How do I run a spring-boot-rest application from a different module eg: in CI build via Maven? - spring-boot

|--Integration tests
|--Spring boot rest application
I have two modules,
Spring boot application is where I have the end points,
it runs on its own embedded tomcat, I want to be able to run it as a part of Integration test's maven build and run integration tests on it.
My question is, is there a way to run spring boot application from a different module via maven?
On Spring boot's website I can only see an example of running a spring-boot application through its own pom by using spring-boot-maven-plugin, but not by running the application as a part of different module by specifiying a jar file with in the execution.

Yes, there are several ways to do what you ask, for example:
use the #SpringBootTest annotation on your test classes (since Spring Boot 1.4);
programmatically start the Spring Boot application from within your test.
The first is my favorite one and the simpler one as well but it only works in the context of unit tests, of course. Here's an example.
Let's assume that you have a class named Application annotated with #SpringBootApplication in your REST module. You can test the endpoints by just defining a test like this inside your Integration test module:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class, properties = {"my.overriden.property=true"} )
public class RestEndpointTest
{
// ...
}
By doing so, the entire context of the application will start. You can then further configure your test depending on your needs, also overriding some properties (see my.overridden.property).
Alternatively, you can define your own configuration inside the test module, referencing any required class from the other module, for example:
#Configuration
#ComponentScan(basePackageClasses = {BaseClass.class})
#EnableJpaRepositories
#EntityScan
#EnableAutoConfiguration
public class SupportConfiguration
{
#Bean
public ARequiredBean bean()
{
return new ARequiredBean();
}
// etc...
}
and the using it just like you would do with any other context:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = SupportConfiguration.class)
public class CustomTest
{
// ...
}
The other method would be to programmatically start an instance of your REST application, with something like this:
public static void main(String[] args) throws IOException
{
try (ConfigurableApplicationContext context = SpringApplication.run(Application.class, args))
{
log.info("Server Started. Press <Enter> to shutdown...");
context.registerShutdownHook();
BufferedReader inReader = new BufferedReader(new InputStreamReader(System.in));
inReader.readLine();
log.info("Closing application context...");
context.stop();
}
log.info("Context closed, shutting down. Bye.");
System.exit(0);
}

Related

How to inject values from application.yaml to Junit 5 Test case

I am using Junit5 to write unit test cases in java. I have few values that I have added in application.yaml but I am not able to retrieve them in my test file eg. ownerMasterList value is coming as null but it is present in application.yaml
#ExtendWith(MockitoExtension.class)
public class OwnerServiceTest {
#Value("${owner.master-list}")
private ownerMasterList;
#Test
void findAllOwners(){
---test detail
}
}
Using ConfigFileApplicationContextInitializer alone does not provide support for #Value("${…​}") injection. Its only job is to ensure that application.properties files are loaded into Spring’s Environment. For #Value support, you need to either additionally configure a PropertySourcesPlaceholderConfigurer or use #SpringBootTest, which auto-configures one for you.
From doc
#RunWith(SpringRunner.class)
#ContextConfiguration(initializers = ConfigFileApplicationContextInitializer.class)
public class OwnerServiceTest {...}
Please try providing property source
By reading your code, it seems that you are using JUnit5 (#ExtendWith(MockitoExtension.class)).
The correct extension to use to trigger the launch of a Spring application context is (#ExtendWith(SpringExtension.class)). If you are using Spring Boot, you can use the #SpringBootTest annotation which is itself annotated with #ExtendWith(SpringExtension.class).
One point to note though: you should not wire an application context when unit testing your code. You should rather test your class in isolation and inject the needed property (here ownerMasterList) through the constructor (see #BeforeEach JUnit 5 annotation):
class OwnerServiceTest {
private OwnerService ownerService;
#BeforeEach
void setUp() {
this.ownerService = new OwnerService(new ArrayList<>(...));
}
}

Adding legacy singleton to Spring ApplicationContext for Injection

I am trying to create a lightweight web service around a legacy java library to expose it as a web service using Spring Boot. I am new to Spring, while I have a lot of java experiance writing libraries all my web service experiance is in ASP.NET.
I can instantiate an instance of my library object but I can't figure out how to then have that object be injected into my controllers via #Autowired when the application is spun up.
This is my main application:
#SpringBootApplication
public class ResolverWebServiceApplication {
private static ArgumentParser newArgumentParser() {
ArgumentParser parser = ArgumentParsers.newFor("Resolver").build();
// configuring the parser
return parser;
}
public static void main(String[] args) throws ArgumentParserException {
ArgumentParser parser = newArgumentParser();
Namespace ns = parser.parseArgs(args);
ResolverOptions options = new ResolverOptions.Builder(ns)
.build();
ResolverContext context = new ResolverContext(options);
// ^^^ I need to get this injected into my controllers ^^^
SpringApplication.run(ResolverWebServiceApplication.class, args);
}
}
And then a simple controller which needs the class injected:
#RestController
public class VersionController {
#Autowired
private ResolverContext context; // And here the instance needs to be injected.
#GetMapping(path = "/version", produces = MediaType.APPLICATION_JSON_VALUE)
public long version() {
return context.getResolver().getVersionAsLong();
}
}
I could make the context a singleton which the controllers just refer to but I want to be able to test my controllers by mocking the context. There is also obviously a lot of validation and error handeling that needs to be added.
I can't have it be a Bean since I only want to instantiate one for my entire application.
The closest question I have found is this one: Registering an instance as 'singleton' bean at application startup. But I can't put the options in the configuration files. The application might be spun up in a container or on a users machine and requires the ability to accept arguments to initialize the library class. It would be a real usability degradation if someone had to manually edit the application config for these options.
You need to tell spring to consider the required classes from your lib when initializing the application context i.e Configure and let spring know how to create a bean and then let spring handle dependency injection for you.
First of all, add required jar that you have in your build file, say pom.xml for maven, in your current project. Idea is to have it on your classpath when you build the project.
As you said it is legacy lib and I am assuming it is not a spring bean, then
In your configuration class, return it as a bean, using #Bean annotaion.
#Configuration
public class YourConfigurationClass {
#Bean
SomeBean returnSomeBeanFromLegacyLib() {
return new SomeClassFromLib();
}
Once you return this bean from your config, it should be available to Spring Context for dependency injection whereever you #Autowire the required dependency.

How to exclude/disable a specific auto-configuration in Spring boot 1.4.0 for #DataJpaTest?

I am using the #DataJpaTest from Spring for my test which will then use H2 as in memory database as described here . I'm also using Flyway for production. However once the test starts FLyway kicks in and reads the SQL file. How can I exclude the FlywayAutoConfiguration and keep the rest as described here in spring documentation in order to let Hibernate create the tables in H2 for me?
#RunWith(SpringRunner.class)
#DataJpaTest
public class MyRepositoryTest {
#Autowired
private TestEntityManager entityManager;
#Autowired
private MyRepository triggerRepository;
}
Have you tried the #OverrideAutoConfiguration annotation?
It says it "can be used to override #EnableAutoConfiguration".
I'm assuming that from there you can somehow exclude FlywayAutoConfiguration
like so:
#EnableAutoConfiguration(exclude=FlywayAutoConfiguration.class)
Adding the dependency on an in-memory database to my build.gradle
e.g. testRuntime "com.h2database:h2:1.4.194"
And adding flyway.enabled=false to application.properties in src/test/resources worked for me.
I am converting an old JDBC app into a spring-data-jpa app and I'm working on the first tests now. I kept seeing a security module instantiation error from spring-boot as it tried to bootstrap the security setup, even though #DataJpaTest should theoretically be excluding it.
My problem with the security module probably stems from the pre-existing implementation which I inherited using PropertySourcesPlaceholderConfigurer (via my PropertySpringConfig import below)
Following the docs here:
http://docs.spring.io/spring-boot/docs/1.4.x/reference/htmlsingle/#test-auto-configuration
and your comments on #LiviaMorunianu's answer, I managed to work my way past every spring-boot exception and get JUnit to run with an auto-configured embedded DB.
My main/production spring-boot bootstrap class bootstraps everything including the stuff I want to exclude from my tests. So instead of using #DataJpaTest, I copied much of what it is doing, using #Import to bring in the centralized configurations that every test / live setup will use.
I also had issues because of the package structure I use, since initially I was running the test which was based in com.mycompany.repositories and it didn't find the entities in com.mycompany.entities.
Below are the relevant classes.
JUnit Test
#RunWith(SpringRunner.class)
#Transactional
#Import({TestConfiguration.class, LiveConfiguration.class})
public class ForecastRepositoryTests {
#Autowired
ForecastRepository repository;
Forecast forecast;
#Before
public void setUp() {
forecast = createDummyForecast(TEST_NAME, 12345L);
}
#Test
public void testFindSavedForecastById() {
forecast = repository.save(forecast);
assertThat(repository.findOne(forecast.getId()), is(forecast));
}
Live Configuration
#Configuration
#EnableJpaRepositories(basePackages = {"com.mycompany.repository"})
#EntityScan(basePackages = {"com.mycompany.entity"})
#Import({PropertySpringConfig.class})
public class LiveConfiguration {}
Test Configuration
#OverrideAutoConfiguration(enabled = false)
#ImportAutoConfiguration(value = {
CacheAutoConfiguration.class,
JpaRepositoriesAutoConfiguration.class,
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
HibernateJpaAutoConfiguration.class,
TransactionAutoConfiguration.class,
TestDatabaseAutoConfiguration.class,
TestEntityManagerAutoConfiguration.class })
public class TestConfiguration {
// lots of bean definitions...
}
PropertySpringConfig
#Configuration
public class PropertySpringConfig {
#Bean
static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer()
throws IOException {
return new CorePropertySourcesPlaceholderConfigurer(
System.getProperties());
}
}
In my particular case, i needed to disable the FlywayDB on in-memory integration tests. These are using a set of spring annotations for auto-configuring a limited applicationContext.
#ImportAutoConfiguration(value = TestConfig.class, exclude = FlywayAutoConfiguration.class)
the exclude could effectively further limit the set of beans initiated for this test
I had the same problem with my DbUnit tests defined in Spock test classes. In my case I was able to disable the Flyway migration and managed to initialize the H2 test database tables like this:
#SpringBootTest(classes = MyApplication.class, webEnvironment = SpringBootTest.WebEnvironment.NONE,
properties = ["flyway.enabled=false", "spring.datasource.schema=db/migration/h2/V1__init.sql"])
I added this annotation to my Spock test specification class. Also, I was only able to make it work if I also added the context configuration annotation:
#ContextConfiguration(classes = MyApplication.class)
I resolved the same issue by excluding the autoconfiguration from my application definition, i.e.
#SpringBootApplication(exclude = {FlywayAutoConfiguration.class})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
you can also sue the following annotation:
#RunWith(SpringRunner.class)
#DataJpaTest(excludeAutoConfiguration = {MySqlConfiguration.class, ...})
public class TheClassYouAreUnitTesting {
}
You can just disable it in your test yaml file:
flyway.enabled: false

Conditional ComponentScan on package

In a Spring Boot Application I have a package with Application class like
#SpringBootApplication
class Application {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(Application);
application.run(args);
}
}
which automatically has ComponentScan set up from that class's package by default. Then I have several subpackages each containing several component and service beans (using annotations). But for the purpose of reuse of this application for different use cases, I need to enable/disable all the components in some of the subpackages, preferably by a property.
That is I have subpackages like
org.example.app.provider.provider1
org.example.app.provider.provider2
Now, based on some property I would like to enable (scan for) beans in one of the packages, e.g.
provider1.enabled=true
I thought I could make ConditionalOnProperty on Configuration class work like that, but the problem is, that the beans are picked up by the default #SpringBootApplication component scan (i.e. the subpackage Configuration class does not override the top level one)
So I thought I would exclude the packages, but this adds more work (and knowledge) when a new provider package is needed (need to know in advance to add an explicit exclude for that package).
Is there any other way how to do this I can't figure out?
Load the provider components conditionally
A Spring Configuration annotated with a #ConditionalOnProperty would do just that:
#Configuration
#ComponentScan("org.example.app.provider.provider1")
#ConditionalOnProperty(name = "provider1.enabled", havingValue = "true")
public class Provider1Configuration {
}
#Configuration
#ComponentScan("org.example.app.provider.provider2")
#ConditionalOnProperty(name = "provider2.enabled", havingValue = "true")
public class Provider2Configuration {
}
Then exclude the components under org.example.app.provider.*
Now, all you need is to exclude the providers from Spring Boot Application (and let the CondtionalOnProperty do its work). You can either:
(1) move the provider packages so that they are not below the Spring Boot Application
For example, if the Spring Boot main is in org.example.app, keep the #Configuration in org.example.app but the providers in org.example.providers
(2) Or exclude the provider package from Spring Boot (assuming the #Configuration are in org.example.app for example):
SpringBootMain.java:
#SpringBootApplication
#ComponentScan(excludeFilters = #ComponentScan.Filter(type = FilterType.ASPECTJ, pattern = "org.example.app.provider.*"))
class Application {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(Application);
application.run(args);
}
}

#IntegrationTest properties in a suite are not reloaded

I've put an #IntegrationTest annotation on every test, and sometimes I use it to add properties to the environment. When running all tests together, it seems that only the properties encountered in the first #IntegrationTest annotation are used, so some tests are failing. Is there a way to force a reload of those properties?
This is an example of my usage:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes=TestApplication.class)
#WebAppConfiguration
#IntegrationTest("some.property=true")
public class SomeIntegrationTest {
Spring Boot Applications under test are started just once for all tests, which is a good thing regarding test performance. If you want to start another application with a different property set, you have to write another Spring Boot application class like this:
#Configuration
#EnableAutoConfiguration
public class MetricsTestApplication {
public static void main(String[] args) {
SpringApplication.run(MetricsTestApplication.class, args);
}
}
In the integration test, you reference that other class. In addition you have to set a different port than the first application has, and you can add the properties that differ from the properties of the first application:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes=MetricsTestApplication.class)
#WebAppConfiguration
#IntegrationTest({"server.port=8091","batch.metrics.enabled=true"})
Our MetricsTestApplication with the different property set is now started under port 8091.
According to the API documentation #IntegrationTest annotation is "signifying that the tests are integration tests (and therefore require an application to startup "fully loaded" and listening on its normal ports)".
If you want to use reloadable properties, you should use EnvironmentTestUtils.
eg.
#Autowired
Environment env;
#Autowired
ConfigurableApplicationContext ctx;
#Before
public void before() {
EnvironmentTestUtils.addEnvironment(ctx, "test.value:myValue");
}
#Test
public void testGreeting() {
assertThat(env.getProperty("test.value"), comparesEqualTo("myValue"));
}

Resources