can spring do this guice-like override of bindings? - spring

In Guice, I have a ProductionModule with my bindings. In my tests, I can load that PLUS a module that overrides a few of the production bindings with mock objects. How do I do such a thing in spring....
For example, load production-spring.xml in test files and then have the test load in test-spring.xml which would ONLY override some of the bindings in production-spring.xml
This tests the integration and make sure changes in production-spring.xml don't break things. These are more automated integration tests then testing a unit and work extremely well.

You can override beans by listing multiple xml files. Beans in the later files will override those loaded before.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(
locations = {"classpath:prodDB.xml",
"classpath:applicationContext.xml",
"classpath:testDb.xml"})
public class SpringTest {
#Autowired
protected DataSource dataSource; //uses the datasource from testDb.xml
}
So in this case testDB.xml overrides the DataSource configured in prodDb.xml. This applies even if you don't use the SpringJUnit4ClassRunner as well:
new ClassPathXmlApplicationContext(new String[]
{"classpath:prodDb.xml",
"classpath:testDb.xml"});
Using a tool like Constretto you can do the same with annotated beans:
#Service
public class FooService...
#Service
#Environment("test")
public class FakeFooService ...
Now, if you run a test with the #Environment("test") annotation on the class, the FakeFooService will be used.

In test environment, you can add your Spring context xml with with overwritten beans before other declarations. And there is also external properties you can change for each environment.

Related

how to correctly modularize app configuration, so that tests(IT,datajpa,...) does not pick up everything intended for production

In our app I found out, that my integration tests picks up more stuff than I'd like. I'd like to know, how correctly structured app configuration looks like, what do you use, so that I can #Import in tests only those configuration which are used in production, which are needed.
I believe relevant page in documentation is:
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-testing-spring-boot-applications-testing-user-configuration
... it's stressed there, that it's important to structure code in sensible way, however it's not shown that much, what that is/means. I know about profiles and can probably create profile which would be unmatched in tests and import manually, but that's probably not that sensible way they were talking about.
Consider this main entrypoint:
#SpringBootApplication
public class DemoApplication {
private final SomeService someService;
public DemoApplication(SomeService someService) {
this.someService = someService;
}
#EventListener(ApplicationReadyEvent.class)
public void started() {
System.out.println(someService.doIt());
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
interface of some service:
public interface SomeService {
public String doIt();
}
and configuration:
#Configuration
public class Config {
#Bean
public SomeService createSomeServiceBean() {
return new SomeService() {
#Override
public String doIt() {
return String.format("Hi! (At %s)", LocalDateTime.now());
}
};
}
}
When invoked, entrypoint annotated by #SpringBootApplication will do component scan, will discover configuration and it will work. Reading further in documentation we will find sentence: Test slices exclude #Configuration classes from scanning([if #ComponentScan does have default value of basePackages and basePackagesClasses]), however following test:
#SpringBootTest
class DemoApplicationTests {
#Autowired
private SomeService someService;
#Test
void contextLoads() {
System.out.println(someService.doIt());
}
}
just happily discovers SomeService bean defined. Or did that sentence meant just that tests annotated by for example #DataJpaTest won't register some configurations? Kinda unclear to me, but it does not seem possible, since how would #DataJpaTest would know, which configurations to ommit and which not.
Again, I know how to use profiles/excluding configurations. I'm asking about "sensible way of structuring app".
How to sensibly structure you app and how to configure it so that:
#SpringBootApplication annotated entrypoint will do component scan, find and use configurations, for production, but these configurations needs to be manually imported in tests?
some packages will be automatically scanned for configurations which will be used both in development and tests environments.
The Spring Boot Test support provides annotations that allow to only create a Spring Context with the relevant beans to testing a specific slice of your application.
There is no specific package structure or naming strategy required to make use of this feature.
Here are some of these:
#DataJpaTest: You get a Spring Context with relevant beans to test your JPA <-> Database interface: EntityManager, DataSource, all your interfaces extending JpaRepository
#WebMvcTest: You get a Spring Context with a mocked servlet environment for testing your web layer that includes the following beans for your: all your controller, controller advice, WebMvcConfigurer , Filter, etc. but not anything that is annotated with e.g. #Service or #Component
#SpringBootTest: This will give you a full Spring Context and tries to create all beans for you. You can exclude some autoconfiguration e.g. if you don't want autoconfiguration to kick in:
Example:
#SpringBootTest(webEnvironment = RANDOM_PORT)
#TestPropertySource(properties=
{"spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration"})
There are way more test slice annotations, you can have a look at here
So these annotations are smart in a way that they know which beans they should include in the context and which to exclude.
A general approach to testing your application can be to use the first two test annotations stated above to verify web and data layer in isolation. Next use Mockito and plain JUnit 5 to unit test your service classes. And finally, write some integration test that creates the whole Spring Context with #SpringBootTest to test everything together.

Are the project beans already instantiated when we try to run a junit test in spring boot

I am new to Spring and spring boot.
For my spring boot application which is a rest controller, I have some beans along with my data source.
I use my data source to create jdbc template. Now when I am in my rest controller code, I have all these beans #Autowired and they work perfectly fine.
My query is regarding the junit testing part.
When I write my test code inside src/test/java and when I execute my test class within IDE, are the beans defined in my src/main/javacode, instantiated before test case execution?
You might use the same container, or instantiate another container particularly for testing purposes, for which you'll provide a configuration of that other Spring Container separately:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("classpath:test-context.xml")
public class SomeClassTest{...}
However, you can also enable support for loading your Application Context and then use the #Autowired fields in your JUnit fixtures, which also works fine too:
#RunWith(SpringRunner.class)
public class SomeTestClass {
....
#Autowired
ApplicationContext context;
....
}
From here, you can get any bean you wish.

Configuration of SpringJUnit4ClassRunner Test clashes with SpringBootTest

I have a bunch of tests in my project that are all annotated with #SpringBootTest and therefore load up a SpringBoot context.
Now recently I refactored a Test in which I wanted a smaller scope (it´s about process coverage with camunda) to #RunWith(SpringJUnit4ClassRunner.class).
Since this means that no context is loaded automatically I create some beans "manually" with a static inner class configuration. The entire test looks something like this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {
ExternalConfiguration.class, MyTest.InternalConfiguration.class
})
public class MyTest{
#Autowired
private SomeBean someInternalBean;
#Configuration
public static class InternalConfiguration{
#Bean
SomeBean someInternalBean() {
return mock(SomeBean .class);
}
}
//Tests
Now, this test runs fine when I run it. BUT when I run any other test ( those still annotated with #SpringBootTest), I get issues with when the ApplicationContext is loaded:
The bean 'someInternalBean', defined in class path resource [.../MyTest$InternalConfiguration.class], could not be registered. A bean with that name has already been defined in file [.../SomeBean.class] and overriding is disabled.
Apparently a bean is created when loading the ApplicationContext because the class is annotated with #Component AND the context loader tries to create another bean from my internal configuration.
I cant allow bean-overriding because my mock beans might overwrite the automatically created beans (which they do, I tried).
How do I circumvent this? I want my SpringJUnit4ClassRunner-tests with their internal configurations to not affect my other #SpringBootTest-tests. I already tried making the configuration beans conditional with #ConditionalOnMissingBean but that did not work.
Turns out those inner configuration classes should not be annotated with #Configuration. Removing the annotation makes it so that the manual bean generation still works and the configuration is no longer picked up by the componentScan.

Spring: how AnnotationConfigWebApplicationContext could not overwrite later beans?

I have a web application that use Sring IoC framework.
I use the Java configuration for Spring, and I only use #Configuration annoted module definition (no DI related tags elsewhere in the code).
The Spring registry is built on web application start-up thanks to (a bit modified version of) Spring context loader listener, and the contextConfigLocation param in web.xml configured to point to the #Configuration annotated class.
All that is good and I get a AnnotationConfigWebApplicationContext.
Now, I want to have plugins in my application, that will have their own #Configuration annotated configuration classes, and will use some of the main application services. BUT I don't want to have main application to be modified to load these new modules.
So, I thought that I could simply use the package searching of annotated class for that, but now, it seems that I can use two beans with the same type, even if they have different ids, and clearly AnnotationConfigWebApplicationContext doc states that:
Note: In case of multiple #Configuration classes, later #Bean definitions will override ones defined in earlier loaded files. This can be leveraged to deliberately override certain bean definitions via an extra Configuration class.
I don't want that, because modules should be able to contribute alternative version of services, not (alwways) override existing one - especcially if I want to have a "moduleDef" bean.
I tried to use differents approach on that, but the hierachy of Context and related services is just to big for me.
So, does anybody know how I could reach my goal ?
Thanks
You can have multiple beans of the same type, but You cannot have 2 or more beans with the same ID in a single Spring ApplicationContext - no matter if You use XML or JavaConfig.
The overriding mechanism matches the bean ID's, so all You need to do is to ensure unique ID, i.e.: coreModuleDef, someOtherModuleDef, anotherModuleDef. I don't think You need the ID of each module definition to be identical? What should be sufficient is the type to be the same, but not ID.
You can also turn off the overriding mechanism by setting allowBeanDefinitionOverriding to false on Your AnnotationConfigWebApplicationContext to get an exception if You accidentally override a bean:
public class MyDispatcherServlet extends DispatcherServlet {
#Override
protected void postProcessWebApplicationContext(
ConfigurableWebApplicationContext wac) {
((AnnotationConfigWebApplicationContext) wac)
.setAllowBeanDefinitionOverriding(false);
}
}
or:
public class MyContextLoaderListener extends ContextLoaderListener {
#Override
protected void customizeContext(
ServletContext servletContext,
ConfigurableWebApplicationContext applicationContext) {
((AnnotationConfigWebApplicationContext) wac)
.setAllowBeanDefinitionOverriding(false);
}
}

How to use spring to resolve dependencies of an object created manually?

I would like to know if it's possible to use Spring to resolve the dependencies of an object created manually in my program. Take a look at the following class:
public class TestClass {
private MyDependency md;
public TestClass() {
}
...
public void methodThaUsesMyDependency() {
...
md.someMethod();
...
}
}
This TestClass is not a spring bean, but needs MyDependency, that is a spring bean. Is there some way I can inject this dependency through Spring, even if I instantiate TestClass with a new operator inside my code?
Thanks
Edit: The method I'm describing in my original answer below is the general way to accomplish DI external of the container. For your specific need - testing - I agree with DJ's answer. It's much more appropriate to use Spring's test support, for example:
#Test
#ContextConfiguration(locations = { "classpath*:**/applicationContext.xml" })
public class MyTest extends AbstractTestNGSpringContextTests {
#Resource
private MyDependency md;
#Test
public void myTest() {
...
While the above example is a TestNG test, there is also Junit support explained in 8.3.7.2. Context management and caching.
General approach: Annotate your class with #Configurable and utilize AspectJ load-time or compile-time weaving. See 6.8.1 in the Spring documentation on AOP for more details.
You can then annotate your instance variables with #Resource or #Autowired. Though they accomplish the same goal of dependency injection, I recommend using #Resource since it's a Java standard rather than Spring-specific.
Lastly, remember to consider using the transient keyword (or #Transient for JPA) if you plan on serializing or persisting the objects in the future. Chances are you don't want to serialize references to your DI'd repository, service, or component beans.
See the autowire() method on the AutowireCapableBeanFactory class. If you use an ClasspathXmlApplicationContext, you can get the factory with getAutowireCapableBeanFactory()
To get the ApplicationContext, you would need to use a static singleton or other central repository, such as JNDI or a Servlet container. See DefaultLocatorFactory on how to get an instance of the ApplicationContext.
If what you need is for testing purposes, Spring has good support for the scenario that you described above.
Check out Spring Reference manual section on Testing

Resources