5

In the following unit test, where I provide properties both manually and try to read them from an existing YAML resource file (different strategies were tried with @TestPropertySource), the @Value{..} properties don't get set and I'm always getting NULL for them:

@SpringBootConfiguration @RunWith(SpringJUnit4ClassRunner.class) @TestPropertySource(properties = { "eligible_filename = xyz" }) @ExtendWith(MockitoExtension.class) public class AppServiceTest { @Value("${eligible_filename}") // This var is always NULL 

as well as

@SpringBootConfiguration @RunWith(SpringJUnit4ClassRunner.class) @TestPropertySource(properties = "application-test.yaml") /* File exists, also tried resources/application-test.yaml */ @ExtendWith(MockitoExtension.class) public class AppServiceTest { @Value("${eligible_filename}") // This var is always NULL 

enter image description here

3 Answers 3

1

Here's what I've finally settled on. We must use @SpringBootTest to load in the test properties (application-test.yaml) and get the @Value(..), but that unfortunately causes the whole application to run. To block out the real objects being used, and substitute mock objects instead, we can use @SpringBootTest with @Import(SomeConfiguration.class) as follows. This will make the mock object get picked up on running the @SpringBootTest test:

@SpringBootTest @Import(value = {MockAppServiceConfiguration.class, MockEmailServiceConfiguration.class}) // etc. any other mocked objects public class MyTest { } 

example of a Mock Service Configuration:

public class MockEmailServiceConfiguration { // Replace with mock EmailService when running full-application @SpringBootTest tests @Bean public EmailService emailService(){ return new EmailService() { //... Override any service methods with a mock result (no real processing) } 

Now you can run the test without getting real objects wired. But since we still need to unit-test the actual service class, I manually create my own Service object and do Mockito's testing on it with openMocks / @Mock / @InjectMocks). Here's Part 2 for testing the actual Service class:

@SpringBootTest @Import(value = {MockAr11ApplicationServiceConfiguration.class, /* Prevent the loading of the real Ar11ApplicationService during @SpringBootTest's full-application launch */ MockEmailServiceConfiguration.class}) /* Prevent the loading of the real EmailService during @SpringBootTest's full-application launch */ @ExtendWith(MockitoExtension.class) public class Ar11ApplicationServiceTest { // Custom object to be constructed for unit testing, includes DAO sub-object via InjectMocks @InjectMocks Ar11ApplicationServiceImpl ar11ApplicationServiceImpl; // Custom DAO sub-object to be constructed for this unit test via Mock @Mock private Ar11ApplicationDAO ar11ApplicationDAO; @BeforeEach void setUp() throws Exception { // Manually construct/wire my own custom objects for unit testing ar11ApplicationServiceImpl = new Ar11ApplicationServiceImpl(); ar11ApplicationDAO = new Ar11ApplicationDAO(); ar11ApplicationServiceImpl.setAr11ApplicationDAO(ar11ApplicationDAO); MockitoAnnotations.openMocks(this); // Set up mock behaviors for the DAO sub-object with when/then when(ar11ApplicationDAO.method(filename)).thenReturn(..) doNothing().when(ar11ApplicationDAO).voidMethod(params); 
Sign up to request clarification or add additional context in comments.

Comments

0

Apparently @TestPropertySource and @PropertySource don't work with YAML files. This is a known issue: https://stackoverflow.com/a/61322522/1005607

I had to hack the solution with SnakeYaml,

 <dependency> <groupId>org.yaml</groupId> <artifactId>snakeyaml</artifactId> </dependency> 

Unit Test: Example: to get "app.sftp.port":

 Yaml yaml = new Yaml(); InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("application-test.yaml"); Map<String, Object> obj = yaml.load(inputStream); inputStream.close(); LinkedHashMap objLevel1 = (LinkedHashMap)obj.get("app"); LinkedHashMap objLevel2 = (LinkedHashMap)objLevel1.get("sftp"); Integer port = (Integer)objLevel2.get("port"); 

It's really weird that I have to do this. You would think that at least the Yaml library would have some quick read methods for properties, but it doesn't, it just spits out a Map. Crazy!

1 Comment

How does this populate a '@Value'? @TestPropertySource works with yaml. I have updated my answer.
0

Your TestPropertySource is setup incorrectly, try locations = "classpath:application-test.yaml". You also need a @SpringBootTest annotation.

The following works:

 import java.io.IOException; import org.junit.jupiter.api.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @SpringBootTest(webEnvironment=WebEnvironment.NONE) @RunWith(SpringJUnit4ClassRunner.class) @TestPropertySource(locations = "classpath:application-test.yaml") public class AppServiceTest{ @Value("${eligible_filename}") String eligibleFilename; @Value("${app.sftp.port}") String appSftpPort; @Test public void testIsEligibleFilenamePopulated() throws IOException { System.out.println(eligibleFilename); System.out.println(appSftpPort); } } 

Content of src/test/resources/application-test.yaml (on the classpath):

eligible_filename: foo.bar app: sftp: port: 1234 

Output:

foo.bar 1234 

3 Comments

The problem is, if I put @SpringBootTest, that will run the entire application. I only want to run this specific Unit Test class, but with those properties retrieved.
To populate '@Value' requires SpringContext - you can reduce the extent with 'webEnvironment=WebEnvironment.NONE. - docs.spring.io/spring-boot/docs/2.1.18.RELEASE/reference/html/… under 47.3. I have updated my answer.
Unfortunately @SpringBootTest(webEnvironment = WebEnvironment.NONE) still runs the entire Spring Boot application -- I verified it.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.