6

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
jlb
  • 19,090
  • 8
  • 34
  • 65
  • I don't understand exactly what behaviour do you want from ResourceLoader in the test? – mvb13 Nov 12 '13 at 11:53
  • I want to call `ResourceLoader#getResource` and get a reference to the same resource as if I were calling it outside of the test context. – jlb Nov 12 '13 at 12:05

4 Answers4

10

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.

Arend v. Reinersdorff
  • 4,110
  • 2
  • 36
  • 40
3

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.

zagyi
  • 17,223
  • 4
  • 51
  • 48
  • It's mean to be a unit test but it's looking more like an integration test because of the hard dependency on `ResourceLoader`. Indeed I do want to fully leverage `ResourceLoader`'s functionality. – jlb Nov 12 '13 at 12:06
  • 1
    Then it's an integration test. :) But putting the terminology aside, the TestContext framework will help you by initializing the Spring context that contains your service instance. In this case there is basically no need for mockito, but it wouldn't be bad practice to use it (e.g. for mocking some other dependency of the service). – zagyi Nov 12 '13 at 12:19
  • Unfortunately, while all that has helped hook the components together, the `ResourceLoader` isn't behaving as it does in the non-test context. I'll update my question with more details – jlb Nov 12 '13 at 12:45
  • What is the exact resource path that you try to resolve? Check if the [`FileSystemResource` caveats](http://docs.spring.io/spring/docs/current/spring-framework-reference/html/resources.html#resources-filesystemresource-caveats) applies to your usecase. – zagyi Nov 12 '13 at 13:06
  • The resource is a file src/main/webapp/. I've updated the question with the differences in resource loaders based on execution context - it looks like the test resource loader doesn't know where to look. – jlb Nov 12 '13 at 13:32
  • Alright, then the problem is that you try to access resources that are not on the classpath at all (files under `src/main/webapp`). At runtime they can only be accessed, because the servlet container provides a way to read files without using a class loader, but your test is obviously running outside the servlet container. Basically the same problem is discussed in detail in [this answer](http://stackoverflow.com/a/14946430/1276804) (with the difference that the desired resources are spring context files in that case). Try using resource paths like `file:src/main/webapp/...` – zagyi Nov 12 '13 at 13:48
1

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.

Debojit Saikia
  • 10,532
  • 3
  • 35
  • 46
  • Can you use Spring's TestContext Framework in addition to something like Mockito? Or would that be considered really bad code smell..? – jlb Nov 12 '13 at 12:10
  • You can of course you Mockito along with Spring's TestContext Framework. The test scenario you have provided is an integration testing. For unit testing, you can use any mocking framework to create your mock objects. This link will give a good idea about this: http://docs.spring.io/spring/docs/3.0.x/reference/testing.html – Debojit Saikia Nov 12 '13 at 12:15
  • Thank you for you answer! Accepting zagyi because they got here first! – jlb Nov 12 '13 at 12:28
0

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)

Ithar
  • 4,865
  • 4
  • 39
  • 40