Inject test beans into main method - spring

I'm using JavaConfig to manage and wire Spring beans into my Java app. The Java application is a main method - and basically runs as a batch job, invoked via a bash file. Is there a way that I can use a different (test) config in my main method?
public static void main(String[] args) {
final ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
// do Stuff
}
I have used the following annotations successfully before in my test classes:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { TestConfig.class })
, but this does not work for "main" applications. Short of passing in the Spring context to use as an argument, not sure what I can do here. Thanks

You should be able to use profiles in your actual config class to do what you want as well.
By setting the desired Profile you can "inject" the different beans you want.
Your ApplicationConfig might look like:
#Configuration
#Import({
JndiDataConfig.class,
TestDataConfig.class,
)
public class ApplicationConfig {
...
where TestDataConfig looks (in part) like:
#Configuration
#Profile("test")
public class TestDataConfig {
#Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
and where JndiDataConfig looks like:
#Configuration
#Profile("production")
public class JndiDataConfig {
#Bean
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}

Related

Spring Boot Injecting Implementations for Prod and Test

I'm new to spring boot and I'm trying to wrap my head around how to make dependency injection work for deployment and testing.
I have a #RestController and a supporting #Service. The service injects another class that is an interface for talking to Kafka. For the Kafka interface I have two implementations: one real and one fake. The real one I want to use in production and the fake in test.
My approach is to use two different configuration for each environment (prod and test).
#Configuration
public class AppTestConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherFakeImpl();
}
}
#Configuration
public class AppConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherImpl();
}
}
Then in my main application I would like to somehow load AppConfiguration.
#SpringBootApplication
public class DeployerServiceApiApplication {
public static void main(String[] args) {
SpringApplication.run(DeployerServiceApiApplication.class, args);
}
// TODO: somehow load here...
}
And in my test load the fake configuration somehow
#SpringBootTest
#AutoConfigureMockMvc(addFilters = false)
public class DeployerServiceApiApplicationTest {
#Autowired private MockMvc mockMvc;
// TODO: somehow load AppTestConfiguration here
#Test
public void testDeployAction() throws Exception {
...
ResultActions resultActions = mockMvc.perform(...);
...
}
}
I've spent the better part of a day trying to figure this out. What I'm trying to accomplish here is fundamental and should be straight forward yet I keep running into issues which makes me wonder if the way I'm thinking about this is all wrong.
Am not sure if i understand your question completely but from description i guess you wish to initialize bean based on environment. Please see below.
#Profile("test")
#Configuration
public class AppTestConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherFakeImpl();
}
}
#Profile("prod")
#Configuration
public class AppConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherImpl();
}
and then you can pass the "-Dspring.profiles.active=prod" argument while starting you application using java command or you can also specify the profile in your test case like below.
#SpringBootTest
#ActiveProfile("test")
#AutoConfigureMockMvc(addFilters = false)
public class DeployerServiceApiApplicationTest
Use spring profiles, you can annotate your test class with #ActiveProfiles("test-kafka") and your test configuration with #Profile("test-kafka").
This is pretty easy task in spring boot world
Rewrite your classes as follows:
#Profile("test")
#Configuration
public class AppTestConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherFakeImpl();
}
}
#Profile("prod")
#Configuration
public class AppConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherImpl();
}
}
This will instruct spring boot to load the relevant configuration when the "prod"/"test" specified.
Then you can start your application in production with --spring.profiles.active=prod and in the Test you can write something like this:
#SpringBootTest
#ActiveProfiles("test")
public class DeployerServiceApiApplicationTest {
...
}
If you want to run all the tests with this profile and do not want to write this ActiveProfiles annotation you can create src/test/resources/application.properties and put into it: spring.active.profiles=test

How to change the implementing class when using annotations with Spring?

I'm used to Spring with xml configuration. With xml, I can have one main implementation and one test implementation for a class, so that the test implementation will be used for JUnit tests, how can I do this with annotations ? Cause it looks like the implementation is already chosen in the "#qualifier" annotation ?
Let's take an example :
<bean id="myService" class="example.Service" />
<bean id="myHibernateDao" class="example.HibernateDao" />
<bean id="myStubDao" class="example.StubDao" />
In xml config, I can have this in src/main/resources :
<bean id="myService" class="example.Service">
<ref="myHibernateDao" />
</bean>
And this in src/test/resources :
<bean id="myService" class="example.Service">
<ref="myStubDao" />
</bean>
How can I do this with annotations, if I have already declared #Qualifier("myHibernateDao") into my service class ?
As explained in the comment above and in spring blog, you can do this via #Profile annotation.
Please find a sample config from the example below,
DataConfig.java
interface DataConfig {
DataSource dataSource();
}
StandaloneDataConfig .java
#Configuration
#Profile("dev")
public class StandaloneDataConfig implements DataConfig {
#Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
JndiDataConfig.java
#Configuration
#Profile("production")
public class JndiDataConfig implements DataConfig {
#Bean
public DataSource dataSource() {
try {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
} catch (NamingException ex) {
throw new RuntimeException(ex);
}
}
}
TransferServiceConfig.java
#Configuration
public class TransferServiceConfig {
#Autowired
DataConfig dataConfig;
#Bean
public TransferService transferService() {
return new DefaultTransferService(accountRepository(), feePolicy());
}
#Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataConfig.dataSource());
}
#Bean
public FeePolicy feePolicy() {
return new ZeroFeePolicy();
}
}
Setting bean profile to an application context
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setDefaultProfiles("dev");
ctx.register(TransferServiceConfig.class, StandaloneDataConfig.class,
JndiDataConfig.class);
ctx.refresh();
Since the bean profile has been set to Dev, The TransferServiceConfigwill be injected with StandaloneDataConfig
You can basically do the same thing with #Configuration classes.
Production Setup
Let's assume you have many different DAO implementations that might or might not implement a common interface (it doesn't matter). Let's further assume that MyServiceDao is the concrete implementation that your Service class needs.
Write the following configuration classes:
#Configuration
public class MyServiceConfiguration {
#Bean
public Service myService(MyServiceDao dao) {
return new Service(dao);
}
}
-
#Configuration
public class MyProductionServiceDaoConfiguration {
#Bean
public MyServiceDao myServiceDao() {
return new MyServiceDao();
}
}
Methods in #Configuration classes that are annotated with #Bean are eligible for Spring's auto-wiring. In the MyServiceConfiguration above, Spring will use the type of the method parameter to find a matching bean. If you include MyProductionServiceDaoConfiguration when creating the Spring context, this will be the instance of MyServiceDao that myServiceDao() created.
Test Setup
In your tests, you want to replace MyServiceDao with a stub. The stub needs to extend MyServiceDao so that Spring can find the right bean based on types. Let's call the stub MyServiceDaoStub. Whether you create it using a library like Mockito (which can also create stubs, not just mocks) or actually write an extension of MyServiceDao is up to you.
Instead of including MyProductionServiceDaoConfiguration in your Spring configuration, use the following class:
#Configuration
public class MyTestServiceDaoConfiguration {
#Bean
public MyServiceDao myServiceDao() {
return new MyServiceDaoStub();
}
}
In your test use #ContextConfiguration to load the test setup:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { MyServiceConfiguration.class, MyTestServiceDaoConfiguration.classs })
public class MyServiceTest {
// your tests
}
Using #Autowired
Spring will also process #Autowired annotations in objects returned from #Bean annotated methods. If your Service looks like this:
public class Service {
#Autowired
private MyServiceDao dao;
// more code
}
you can change the myService() method to:
#Bean
public Service myService() {
return new Service();
}

Initializing Spring in not-web annotation-configured application

My java app is using Spring stereotype annotations (#Controller, #Component) and autowire annotations to manage dependency injections.
It is not web application, just plain jar. Also it's pure-annotation based code, i.e. no xml at all.
What is right way to initialize Spring annotation based application context and default configuration just from the main method?
Use #Configuration to name a AppConfig which is equivalent to applicationContext.xml.
#Configuration
public class AppConfig {
#Bean(initMethod = "init")
public Foo foo() {
return new Foo();
}
#Bean(destroyMethod = "cleanup")
public Bar bar() {
return new Bar();
}
}
And in main method to new a AnnotationConfigApplicationContext.
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
Foo foo = ctx.getBean(Foo.class);
//etc
}

Best way to test JPA Spring 3.1

I have written several JPA Repositories, Services and support classes for my Spring 3.1.1/JPA/Hibernate 4 web app. However, I really want to write some unit and integration tests for it (I know, you are supposed to write those first). I am using JavaConfig rather than XML, so I am wondering the best way to test. Here is the particular problem I am trying to solve:
I have a #Configuration that declares DataSource, JpaTransactionManager, LocalContainerEntityManagerFactoryBean, and all my respositories. Obviously I don't want to start all that up for an integration test, so I thought I could use the EmbeddedDatabase and H2 to create an in memory database, populate with values, and then use my Repositories against it. However, the documentation I have seen hasn't helped me put this together. I have this:
#RunWith( SpringJUnit4ClassRunner.class )
public class TestMenuService {
private EmbeddedDatabase database;
#Autowired
private MenuRepository menuRepository;
#Before
public void setUp() throws Exception {
database = new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).setName("myschema")
.addScript("classpath:schema.sql").build();
menuRepository = new MenuRepository();
Assert.assertNotNull(database);
}
But the menuRepository does not get instantiated, so I tried creating a test version of my #Configuration
#Configuration } )
#ComponentScan( basePackages = { "com.mycompany.service"} )
#EnableTransactionManagement
#ImportResource( "classpath:applicationContext.xml" )
#PropertySource( "classpath:test-application.properties" )
public class TestEdmConfiguration {
#Bean
MenuRepository menuRepository() {
return new MenuRepository();
}
My test-applicationContext.xml
<jpa:repositories base-package="com.mycompany.servce.repository"/>
My test-application.properties:
db.driver=org.h2.Driver
db.username=sa
db.password=
db.url=jdbc:h2:mem:myschema
hibernate.dialect=org.hibernate.dialect.H2Dialect
hibernate.format_sql=true
hibernate.hbm2ddl.auto=create
hibernate.ejb.naming_strategy=org.hibernate.cfg.ImprovedNamingStrategy
hibernate.show_sql=true
But this requires that I create all the datasources, etc mentioned above. It seems like I am just duplicating all the support beans for this one.
Is there a way to have the reposository and embeddeddatabase isolated to my test without all the other dependencies?
If you want to test your repository in a full integration test I would image that you need everything else to be setup i.e. EntityManagerFactory, PlatformTransactionManager etc.
Since you are using Spring 3.1 I would suggest that you achieve this using bean profiles.
I would create two profiles one for tests and one for the application, each of which supplies a datasource.
#Configuration
#Profile("test")
public class EmbeddedDataSource {
#Bean
public DataSource dataSource() {
// Return a H2 datasource
}
}
#Configuration
#Profile("application")
public class ApplicationDataSource {
#Bean
public DataSource dataSource() {
// Return a normal datasource
}
}
The you can create a test which starts up the spring context as follows:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = { MyConfigClass.class })
#ActiveProfiles(profiles = {"test"})
#TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
public class TestRepository {
}
In here you can specify the profiles where are active for the test.

Custom spring property source does not resolve placeholders in #Value

I'm trying to build a Spring 3.1 PropertySource which reads its values from Zookeeper nodes. For connecting to Zookeeper I am using Curator from Netflix.
For that I've built a custom property source which reads the value of a property from Zookeeper and returns it. This works fine when I am resolving the property like this
ZookeeperPropertySource zkPropertySource = new ZookeeperPropertySource(zkClient);
ctx.getEnvironment().getPropertySources().addLast(zkPropertySource);
ctx.getEnvironment().getProperty("foo"); // returns 'from zookeeper'
However, when I try to instantiate a bean which has a field with an #Value annotation then this fails:
#Component
public class MyBean {
#Value("${foo}") public String foo;
}
MyBean b = ctx.getBean(MyBean.class); // fails with BeanCreationException
This problem has most likely nothing to do with Zookeeper but with the way I'm registering the property sources and creating the beans.
Any insight is highly appreciated.
Update 1:
I'm creating the app context from an XML file like this:
public class Main {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ctx.registerShutdownHook();
}
}
The class which connects to Zookeeper is a #Component.
#Component
public class Server {
CuratorFramework zkClient;
public void connectToZookeeper() {
zkClient = ... (curator magic) ...
}
public void registerPropertySource() {
ZookeeperPropertySource zkPropertySource = new ZookeeperPropertySource(zkClient);
ctx.getEnvironment().getPropertySources().addLast(zkPropertySource);
ctx.getEnvironment().getProperty("foo"); // returns 'from zookeeper'
}
#PostConstruct
public void start() {
connectToZookeeper();
registerPropertySource();
MyBean b = ctx.getBean(MyBean.class);
}
}
Update 2
This seems to work when I'm using XML-less configuration, i.e. #Configuration, #ComponentScan and #PropertySource in combination with an AnnotationConfigApplicationContext. Why doesn't it work with a ClassPathXmlApplicationContext?
#Configuration
#ComponentScan("com.goleft")
#PropertySource({"classpath:config.properties","classpath:version.properties"})
public class AppConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
Answering to your Update 2: This does not work with your original configuration(registering a PropertySource using #PostConstruct) because the PropertySource is being registered very late, by this time your target bean has already been constructed and initialized.
Typically the injection of the placeholders happens via a BeanFactoryPostProcessor which is very early in the Spring lifecycle(beans have not been created at this stage) and if a PropertySource is registered at that stage, then placeholders should be resolved.
The best approach though is to use a ApplicationContextInitializer, get a handle on the applicationContext and to register the propertySource there:
public class CustomInitializer implements ApplicationContextInitializer<ConfigurableWebApplicationContext> {
public void initialize(ConfigurableWebApplicationContext ctx) {
ZookeeperPropertySource zkPropertySource = new ZookeeperPropertySource(zkClient);
ctx.getEnvironment().getPropertySources().addFirst(zkPropertySource);
}
}

Resources