Using Spring's ResourceLoader in tests - java

I'm #Autowireing a org.springframework.core.io.ResourceLoader into one of my #Service classes.
During the tests, I'd like to get access to an instance of a ResourceLoader, so that it can be injected into the service being tested. What's the best way to get a fully functional instance of ResourceLoader during your tests?
If that's not possible, is there an alternative to ResourceLoader? Essentially, I need to have my service read some static files from the project.
Update:
Started using #RunWith(SpringJUnit4ClassRunner.class) + #ContextConfiguration on my test; however, the ResourceLoader that is now being injected via #Autowire into my service behaves differently than usual (ie. when it's not in the test context). In the test, ResourceLoader#getResource returns a Resource pointing to bad relative path, rather than the proper absolute path which appears during a regular execution.
Some more information discovered while debugging:
During tests, the #Autowired resourceLoader is an instanceof org.springframework.context.support.GenericApplicationContext
During regular execution, resourceLoader is an instance of org.springframework.web.context.support.XmlWebApplicationContext

You can use a DefaultResourceLoader. It handles URL and classpath resources which should be enough for simple tests.
DefaultResourceLoader doesn't need any special setup. Just create a new instance and pass it into your test.

What kind of test do you want to write?
If it's a unit test, you should probably mock ResourceLoader and inject that mock into the service instance. (Use mockito for example)
If it's an integration test, you would be better off using the Spring TestContext framework. Create a spring context that contains all components needed for the test, then annotate your test class with #RunWith(SpringJUnit4ClassRunner.class) +
#ContextConfiguration, which will make it possible to autowire fully configured beans (e.g. the service instance to be tested) in the test class.

I suppose you have your service defined as something like this:
public class ResourceService {
#Autowired
ResourceLoader loader;
}
Now, when you write your test for ResourceService :
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({ "classpath:test-config.xml" })
public class ResourceServiceTest {
#Autowired
ResourceService resourceService;
#Test
public void test() {
...
}
}
The Spring TestContext Framework configures instances of your test classes via Dependency Injection. So, when you autowire ResourceService in your test class, Spring will inject the autowired ResourceLoader property into ResourceService.

ENVIRONMENT:
Java: 17
Springboot: 2.6.2
Junit-jupiter:5.8.2
Generally integration tests will need to start up a container to execute the test cases. This can be achieved using the following code:
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.io.ResourceLoader;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import static org.junit.jupiter.api.Assertions.assertTrue;
#RunWith(SpringRunner.class)
#SpringBootTest
#TestPropertySource(locations = "classpath:application-integration-test.properties")
class TemplateKeyValidatorIT {
#Autowired
ResourceLoader resourceLoader;
#Autowired
private ResourceLoaderService resourceLoaderService;
#Test
void testResourceLoading() {
// Given
String fileName = "myTestFile.txt";
// When
File file = resourceLoaderService.load(fileName);
// Then
assertTrue(file.exists());
}
}
#RunWith(SpringRunner.class): SpringRunner is the base Spring framework Runner. It extends SpringJUnit4ClassRunner, but it’s just an alias to that class.
#SpringBootTest: Annotation is needed to bootstrap the entire container; it creates an ApplicationContext that will be used by the integration tests.
#TestPropertySource(locations = "classpath:application-integration-test.properties"): Loads integration specific application properties.
Note: this will boot strap the real application context but you can separate test configuration class using:
#TestConfiguration
public class MyTestContextConfiguration {
#Bean
public ResourceLoaderService resourceLoaderService() {
return new ResourceLoaderService() {
// implement methods
};
}
}
And this can then be impoerted by your test class using:
#Import(MyTestContextConfiguration.class)

Related

Injecting non-mocks defined by a #Configuration class into a unit test

Java 8, JUnit 4 and Spring Boot 2.3 here. I have a situation where I have a #Component-annotated Spring Boot class that gets #Autowired with all its dependencies (the beans are defined in a #Configuration-annotated config class):
#Configuration
public class SomeConfig {
#Bean
public List<Fizz> fizzes() {
Fizz fizz = new Fizz(/*complex logic here*/);
return new Collections.singletonList(fizz);
}
#Bean
public Buzz buzz() {
return new Buzz(/*complex logic here*/);
}
}
#Component
public class ThingDoerinator {
#Autowired
private Lizz<Fizz> fizzes;
#Autowired
private Buzz buzz;
public String doStuff() {
// uses fizz and buzz extensively inside here...
}
}
I can easily write a unit test to inject all of these dependencies as mocks:
public class ThingDoerinatorTest extends AbstractBaseTest {
#Mock
private List<Fizz> fizzes;
#Mock
private Buzz buzz;
#InjectMocks
private ThingDoerinator thingDoerinator;
#Test
public void should_do_all_the_stuff() {
// given
// TODO: specify fizzes/buzz mock behavior here
// when
String theThing = thingDoerinator.doStuff();
// then
// TODO: make some assertions, verify mock behavior, etc.
}
}
And 80% of the time that works for me. However I am now trying to write some unit tests that are more like integration tests, where instead of injected mocks, I want the beans to be instantiated like normal and get wired into the ThingDoerinator class like they would be in production:
public class ThingDoerinatorTest extends AbstractBaseTest {
#Mock
private List<Fizz> fizzes;
#Mock
private Buzz buzz;
#InjectMocks
private ThingDoerinator thingDoerinator;
#Test
public void should_do_all_the_stuff() {
// given
// how do I grab the same Fizz and Buzz beans
// that are defined in SomeConfig?
// when -- now instead of mocks, fizzes and buzz are actually being called
String theThing = thingDoerinator.doStuff();
// then
// TODO: make some assertions, verify mock behavior, etc.
}
}
How can I accomplish this?
You can use SpringBootTest.
#SpringBootTest(classes = {Fizz.class, Buzz.class, ThingDoerinator.class})
#RunWith(SpringRunner.class)
public class ThingDoerinatorTest {
#Autowired
private Fizz fizz;
#Autowired
private Buzz buzz;
#Autowired
private ThingDoerinator thingDoerinator;
}
You don't need to mock anything, just inject your class in your test and your configuration will provide the beans to your ThingDoerinator class and your test case will work as if you are calling the ThingDoerinator. doStuff() method from some controller or other service.
TL;DR
I think, you are confused with tests running whole spring context and unit test which just test the function/logic we wrote without running whole context. Unit tests are written to test the piece of function, if that function would behave as expected with given set of inputs.
Ideal unit test is that which doesn't require a mock, we just pass the input and it produces the output (pure function) but often in real application this is not the case, we may find ourselves in situation when we interact with some other object in our application. As we are not about to test that object interaction but concerned about our function, we mock that object behaviours.
It seems, you have no issue with unit test, so you could mock your List of bean in your test class ThingDoerinator and inject them in your test case and your test case worked fine.
Now, you want to do the same thing with #SpringBootTest, so I am taking a hypothetical example to demonstrate that you don't need to mock object now, as spring context will have them, when #springBootTest will load whole context to run your single test case.
Let's say I have a service FizzService and it has one method getFizzes()
#Service
public class FizzService {
private final List<Fizz> fizzes;
public FizzService(List<Fizz> fizzes) {
this.fizzes = fizzes;
}
public List<Fizz> getFizzes() {
return Collections.unmodifiableList(fizzes);
}
}
Using constructor injection here1
Now, my List<Fizzes would be created through a configuration (similar to your case) and been told to spring context to inject them as required.
#Profile("dev")
#Configuration
public class FizzConfig {
#Bean
public List<Fizz> allFizzes() {
return asList(Fizz.of("Batman"), Fizz.of("Aquaman"), Fizz.of("Wonder Woman"));
}
}
Ignore the #Profile annotation for now2
Now I would have spring boot test to check that if this service will give same list of fizzes which I provided to the context, when it will run in production
#ActiveProfiles("test")
#SpringBootTest
class FizzServiceTest {
#Autowired
private FizzService service;
#Test
void shouldGiveAllTheFizzesInContext() {
List<Fizz> fizzes = service.getFizzes();
assertThat(fizzes).isNotNull().isNotEmpty().hasSize(3);
assertThat(fizzes).extracting("name")
.contains("Wonder Woman", "Aquaman");
}
}
Now, when I ran the test, I saw it works, so how did it work, let's understand this. So when I run the test with #SpringBootTest annotation, it will run similar like it runs in production with embedded server.
So, my config class which I created earlier, will be scanned and then beans created there would be loaded into the context, also my service class annotated with #Service annotation, so that would be loaded as well and spring will identify that it needs List<Fizz> and then it will look into it's context that if it has that bean so it will find and inject it, because we are supplying it from our config class.
In my test, I am auto wiring the service class, so spring will inject the FizzService object it has and it would have my List<Fizz> as well (explained in previous para).
So, you don't have to do anything, if you have your config defined and those are being loaded and working in production, those should work in your spring boot test the same way unless you have some custom setup than default spring boot application.
Now, let's look at a case, when you may want to have different List<Fizz> in your test, in that case you will have to exclude the production config and include some test config.
In my example to do that I would create another FizzConfig in my test folder and annotate it with #TestConfiguration
#TestConfiguration
public class FizzConfig {
#Bean
public List<Fizz> allFizzes() {
return asList(Fizz.of("Flash"), Fizz.of("Mera"), Fizz.of("Superman"));
}
}
And I would change my test a little
#ActiveProfiles("test")
#SpringBootTest
#Import(FizzConfig.class)
class FizzServiceTest {
#Autowired
private FizzService service;
#Test
void shouldGiveAllTheFizzesInContext() {
List<Fizz> fizzes = service.getFizzes();
assertThat(fizzes).extracting("name")
.contains("Flash", "Superman");
}
}
Using different profile for tests, hence #ActiveProfile annotation, otherwise the default profile set is dev which will load the production FizzConfig class as it would scan all the packages ( 2Remember the #Profile annotation above, this will make sure that earlier config only runs in production/dev env ). Here is my application.yml to make it work.
spring:
profiles:
active: dev
---
spring:
profiles: test
Also, notice that I am importing my FizzConfiguration with #Import annotation here, you can also do same thing with #SpringBootTest(classes = FizzConfig.class).
So, you can see test case has different values than in production code.
Edit
As commented out, since you don't want test to connect to database in this test, you would have to disable auto configuration for spring data JPA, which spring boot by default does that.
3You can create a another configuration class like
#Configuration
#EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
public TestDataSourceConfiguration {}
And then include that with your #SpringBootTest, this will disable default database setup and then you would be left with last piece of puzzle which is any repository interaction you might be having in your class.
Because now, you don't have auto configuration in your test case, those repository are just interfaces, which you can easily mock inside your test class
#ActiveProfiles("test")
#SpringBootTest
#Import(FizzConfig.class, TestDataSourceConfiguration.class)
class FizzServiceTest {
#MockBean
SomeRepository repository;
#Autowired
private FizzService service;
#Test
void shouldGiveAllTheFizzesInContext() {
// Use Mockito to mock behaviour of repository
Mockito.when(repository.findById(any(), any()).thenReturn(Optional.of(new SomeObject));
List<Fizz> fizzes = service.getFizzes();
assertThat(fizzes).extracting("name")
.contains("Flash", "Superman");
}
}
1Constructor injection is advisable instead of `#Autowired` in production code, if your dependencies are really required for that class to work, so please avoid it if possible.
3Please note that you create such configuration only in test package or mark with some profile, do not create it in your production packages, otherwise it will disable database for your running code.

Spring boot test with multiple configuration

In my Spring boot 2.1 project I have different #Configurations for different test (ConfigurationA and ConfigurationB), that reside in different packages. Both configurations define the same set of beans but in a different manner (mocked vs. the real thing)
As I am aware of the Bean overriding mechanism introduced in Spring Boot 2.1, I have set the property: spring.main.allow-bean-definition-overriding=true.
However I do have a test with the following the setup of the following configuration and test class. First there is a #Configuration in the productive part (I'm using Maven):
package com.stackoverflow;
#Configuration
public class ProdConfiguration{
...
}
Then in the test branch there is a general Test #Configuration on the same package level:
package com.stackoverflow
#Configuration
public class TestConfiguration {
#Bean
public GameMap gameMap() {
return Mockito.mock(GameMap.class);
}
}
And in a subpackage I have another #Configuration:
package com.stackoverflow.impl;
#Configuration
public class RealMapTestConfiguration {
#Bean
public GameMap gameMap() {
return new GameMap("testMap.json");
}
}
And then of course there is the test that is troubling me:
package com.stackoverflow.impl;
#ExtendWith(SpringExtension.class)
#SpringBootTest
#ContextConfiguration(classes={RealMapTestConfiguration.class, ProdConfiguration.class})
#ActiveProfiles("bug") // spring.main.allow-bean-definition-overriding=true
public class MapImageServiceIT {
#Autowired
private GameMap map;
}
It turns out that the injected GameMap into my test is a mock instance from TestConfiguration instead of the real thing from RealMapTestConfiguration. Aparrently in my test I have the configuration from ProdConfiguration and TestConfiguration, when I wanted ProdConfiguration and RealMapTestConfiguration. As the beans defined in the ProdConfiguration and *TestConfiguration are different the combination works, but TestConfiguration and RealMapTestConfiguration define the same been. It seems like the TestConfiguration is picked up by component scanning as it is in the same package as ProdConfiguration.
I was under the impression that when overriding beans the bean definition that is closer to the test class would be preferred. However this seems not to be the case.
So here are my questions:
When overriding beans, what is the order? Which bean overrides which one?
How to go about to get the correct instance in my test (using a different bean name is not an option, as in reality the injected bean is not directly used in the test but in a service the test uses and there is no qualifier on it.)
I've not used the spring.main.allow-bean-definition-overriding=true property, but specifying specific config in a test class has worked fine for me as a way of switching between objects in different tests.
You say...
It turns out that the injected GameMap into my test is a mock instance from TestConfiguration instead of the real thing from RealMapTestConfiguration.
But RealMapTestConfiguration does return a mock
package com.stackoverflow.impl;
#Configuration
public class RealMapTestConfiguration {
#Bean
public GameMap gameMap() {
return Mockito.mock(GameMap.class);
}
}
I think the problem here is that including ContextConfiguration nullifies (part of) the effect of #SpringBootTest. #SpringBootTest has the effect of looking for #SpringBootConfiguration in your application (starting from the same package, I believe). However, if ContextConfiguration is applied, then configurations are loaded from there.
Another way of saying that: because you have ContextConfiguration in your test, scanning for #Configuration classes is disabled, and TestConfiguration is not loaded.
I don't think I have a full picture of your configuration setup so can't really recommend a best practice here, but a quick way to fix this is to add TestConfiguration to your ContextConfiguration in your test. Make sure you add it last, so that it overrides the bean definitions in the other two configurations.
The other thing that might work is removing #ContextConfiguration entirely and letting the SpringBootApplication scanning do its thing - that's where what you said about the bean definition that is closest may apply.
In that case just don't use #Configuration on configuration class and import it to the test manually using #Import, example:
#SpringBootTest
#Import(MyTest.MyTestConfig.class)
public class MyTest {
#Autowired
private String string;
#Test
public void myTest() {
System.out.println(string);
}
static class MyTestConfig {
#Bean
public String string() {
return "String";
}
}
}

Spring MVC - Override a bean for one Integration test

I have a project with spring MVC v5.0.8, Java 8
I've made some integration test from controller to database, and now, I want to add one which will test what happens if the first part of a transactional behavior failed. I'll ensure that the transaction is effectively roll-backed.
So, I've to override a DAO to make it fail. After some research, came up a simple idea : override spring config for that test : Overriding an Autowired Bean in Unit Tests
My test work fine now, but the problem is that this configuration is shared with other test from other classes, even if it is in one class. How can I make it specific to that test ?
(If b creation fail, a must be roll-backed)
Failing test :
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#EnableWebMvc
#Sql({"/sqlfiles/clean-data.sql"})
public class AControllerFailDaoIt {
#Configuration
static class ConfigFailDao extends ApplicationConfiguration{
#Bean
#Primary
public BDao getBDao() {
return new BDao() {
//Overriding method to make it fail
};
}
}
#Autowired
AController aController;
#Autowired
ADao aDao;
#Test
public void should_not_create_a_if_b_failed(){
//creation of a
//assert nor a nor b are created
}
}
Nominal test case :
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#EnableWebMvc
#ContextConfiguration(classes = {ApplicationConfiguration.class, CustomWebAppConfiguration.class})
#Sql({"/sqlfiles/clean-data.sql"}) //"/sqlfiles/account-test.sql"
public class AControllerIT {
#Autowired
AController aController;
#Autowired
ADao aDao;
#Autowired
BDao bDao;
#Test
public void should_create_a_and_corresponding_b(){
//create a
//assert that both a and b are created
}
}
ApplicationConfiguration (which is test-specific)
#Configuration
#ComponentScan(basePackages = "my.base.package")
class ApplicationConfiguration implements WebMvcConfigurer {
}
Note : my Integration test classes are within the base package, does it matters ?
I found an option : put the fail test config in an external class, and call it only on my fail test, but it still doesn't work.
At the moment, I ran out of ideas, so I'll welcome your help !
I suggest you to use #Qualifier annotation.
Instead of putting #Primary above getBDao() method in your configuration put #Qualifier with some name i.e.:
#Bean
#Qualifier("BDaoTest")
public BDao getBDao() {
return new BDao() {
//Overriding method to make it fail
};
}
After that put #Primary on your default BDao implementation (in your default config).
Then when you autowire this bean in your test you need to put this qualifier:
#Autowired
#Qualifier("BDaoTest")
BDao bDao;
I got it working. In a bad way, so if you have a better option, I'm in. But I didn't found a way to make my test fail by data... Which would be the better one.
So, I've found that the #Configuration annotation make a class scanned. Spring doc.
I just made my fail configuration in an outer class, and remove the #Configuration annotation. So it won't be scanned by other test configuration. Then in my fail test class, I referenced it in the #ContextConfiguration annotation, so it is used. This way it work fine. I now have a problem with #Transactional, but that's not this thread.
Thanks to responders, you made me look in the right direction :-)

Spring Injecting Beans from src folder in test folders

I have utility classes exposed as beans in my source folders. I want to use some of those utilities in my test classes written in junit 4. For example , I have a utility class that has methods which marshal an object into JSON string. I want to inject this utility bean in my test class. I am unable to inject these beans using Autowired annotation. Should I copy all these classes over to test folder?
Edit:
I am trying to inject jsonUtil. Below is how my code looks like.
import static org.junit.Assert.*;
import java.math.BigDecimal;
#RunWith(MockitoJUnitRunner.class)
#SpringApplicationConfiguration(classes = ProxyApplicationMock.class)
public class ProxyApplicationMock {
#Mock
public SoapClient soapClientMock;
private JsonUtil jsonUtil;
Main Class
public class ProxyApplication {
public static void main(String[] args) {
SpringApplication.run(ProxyApplication.class, args);
}
}
Your main classes can be seen by your test classes, but not the other way around. So no, you don't need to copy them.
If your utility class is declared as a Spring managed bean in your test Spring context configuration (the class -or XML file- declared in the #ContextConfiguration) which may and probably should be different from your main configuration.
Then you can inject it in any Spring managed class, which includes your test classes if it's using the SpringJUnit4ClassRunner.
EDIT:
To sum up what we discussed in the comments, the main problem is that your test runner is not a SpringRunner (alias for SpringJUnit4ClassRunner), and thus JUnit is not running your test in a Spring context. Have a look at a test example here.
The simplest test case will look like this.
#RunWith(SpringRunner.class)
#SpringBootTest
public class CityRepositoryIntegrationTests {
#Autowired
private MySpringBean springBean;
//...
}
But as often with Spring Boot, there's some magic happening behind. #SpringBootTest is a convenient annotation that will detect automatically a class annotated with #SpringBootConfiguration, meaning if you don't have a specific Spring configuration for your test, it will use your main Spring configuration, and thus include and instanciate all the beans for your main app, and that's not usually what we want in a unit test cause we want to test a class independently by mocking its dependencies.
What you can do, is provide the Spring compenent classes you want to include in your tests, as such:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MySpringBean.class)
public class CityRepositoryIntegrationTests {
#Autowired
private MySpringBean springBean;
#Mock
private MyMockedSpringBeanDependency mocked;
//...
}
This question is to Matt, since adding comment is throwing error saying only one additional use can be notified.
NOTE: Not an Answer
I have an Application class & many config classes which are imported in Application class. Earlier it was #Configuration, which I converted to #SpringBootConfiguration in Application class and the actual Config class, whos bean I am trying to mock. Ended up in
java.lang.NoSuchMethodError: org.springframework.boot.SpringApplication.<init>([Ljava/lang/Object;)V
at org.springframework.boot.test.context.SpringBootContextLoader.getSpringApplication(SpringBootContextLoader.java:121)
at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:84)
Please suggest How do I mock, I have the same setup as specified in the ticket.
#Bean
public CacheManager cacheManager()
{
EhCacheManagerFactoryBean factoryBean = new EhCacheManagerFactoryBean();
factoryBean.setConfigLocation(new ClassPathResource("ehcache.xml"));
factoryBean.setShared(true);
return new EhCacheCacheManager(factoryBean.getObject());
}
}

Best way to exclude unit test #Configurations in Spring?

In my Spring Boot project, I have a main class annotated with #SpringBootConfiguration. I also have some unit tests that use #SpringApplicationConfiguration that points to an inner class that defines a Spring context for usage in my unit test (using some mocks).
I now want to write an integration test that starts the full context of my application. However, this does not work as it also picks up the Spring contexts that are defined as inner classes in other unit tests.
What would be the best way to avoid that? I did see the exclude and excludeName properties on #SpringBootConfiguration, but I am unsure how to use them.
UPDATE:
Some code to explain the problem more:
My main class:
package com.company.myapp;
#SpringBootApplication
#EnableJpaRepositories
#EnableTransactionManagement
#EntityScan(basePackageClasses = {MyApplication.class, Jsr310JpaConverters.class})
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
I have a unit test for Spring REST Docs:
package com.company.myapp.controller
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration
#WebAppConfiguration
public class SomeControllerDocumentation {
#Rule
public final RestDocumentation restDocumentation = new RestDocumentation("target/generated-snippets");
// Some test methods here
// Inner class that defines the context for the unit test
public static class TestConfiguration {
#Bean
public SomeController someController() { return new SomeController(); }
#Bean
public SomeService someService() { return new SomeServiceImpl(); }
#Bean
public SomeRepository someRepository() { return mock(SomeRepository.class);
}
So the unit test uses the context defined in the inner class. Now I want a different test that tests if the "normal" application context of the app starts up:
package com.company.myapp;
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(MyApplication.class)
#WebAppConfiguration
public class MyApplicationTest {
#Autowired
private ApplicationContext applicationContext;
#Test
public void whenApplicationStarts_thenContextIsInitialized() {
assertThat(applicationContext).isNotNull();
}
}
This test will now not only wire the stuff it should, but also the beans from the SomeControllerDocumentation.TestConfiguration inner class. This I want to avoid.
You could use profiles: annotate the relevant configuration beans with #Profile("unit-test") and #Profile("integration-test") and inside the tests specify which profile should be active via #ActiveProfiles.
However, it sounds like you could avoid the problem altogether just by reorganising your configuration structure. It's hard to assume anything without seeing any code, though.
Since Spring Boot 1.4, the problem can be avoided by annotation the configuration in the unit tests with #TestConfiguration
I think you talk about #Ignore

Categories

Resources