5

I'm writing a few unit tests for a Spring application that can be run with two different configurations. The two different configurations are given by two application.properties file. I need to write tests twice for each class, since I need to verify that changes that work with a configuration don't impact the other one.

For this reason I created two files in the directory:

src/test/resources/application-configA.properties

src/test/resources/application-configB.properties

Then I tried to load them using two different values of @TestPropertySource:

@SpringBootTest
@TestPropertySource(locations = "classpath:application-configA.properties")
class FooTest {
  @InjectMock
  Foo foo;

  @Mock
  ExternalDao dao;

  // perform test
}

And the Foo class is this one:

@Service
public class Foo {
  @Autowired
  private External dao;

  methodToTest() {
    Properties.getExampleProperty();
    this.dao.doSomething(); // this needs to be mocked!
  }
}

While the class Properties is:

@Component
public class Properties {
  private static String example;

  @Value("${example:something}")
  public void setExampleProperty(String _example) {
    example = _example;
  }

  public static String getExampleProperty() {
    return example;
  }
}

The problem is that Properties.getExampleProperty() always returns null during the test, while it contains the correct value in the normal execution.

I've tried:

  • Setting a default ("something" above)
  • Setting a value in application.properties
  • Setting a value in application-configA.properties of /main
  • Setting a value in application-configA.properties of /test
  • Setting a inline value in @TestPropertySource

Nothing of these worked.

I've read this question's answers, but looks like something different and they did not help me

  • 1
    Ofcourse it does, you are using Mockito not Spring for your test. So the linked question is exactly the same (although that was mocking used through Spring and not directly with Mockito). In short replace `@InjectMocks` with `@Autowired in your test. – M. Deinum Jun 01 '22 at 09:18
  • @M.Deinum ok, but if I replace `@InjectMocks` with `@Autowired`, the mocks won't be replaced, will they? – Christian Vincenzo Traina Jun 01 '22 at 09:26
  • Which mocks.. there are non in your test. – M. Deinum Jun 01 '22 at 09:28
  • @M.Deinum I updated my question to highlight the problem – Christian Vincenzo Traina Jun 01 '22 at 09:28
  • You should really read the documentation... Use `@MockBean` instaed of `@Mock`... This lets SpringBoot control the mock creation and resets and inject those instead of the real implementation. – M. Deinum Jun 01 '22 at 09:28
  • @M.Deinum so `@InjectMock` = `@Autowired` and `@Mock` = `@MockBean`? I had read the docs, but just felt like using `@Autowired` wired the actual object and not the one with mocks :) – Christian Vincenzo Traina Jun 01 '22 at 09:30
  • 1
    [This](https://docs.spring.io/spring-boot/docs/1.5.2.RELEASE/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications-mocking-beans) clearly states what to do. – M. Deinum Jun 01 '22 at 09:32
  • I tried using `@Autowired` and `@MockBean`, and the value is still null. Do I need to tell Spring to load the `Properties` component in the context? – Christian Vincenzo Traina Jun 01 '22 at 09:33
  • 1
    The value cannot be `null` else the application would break. I'm assuming here that `Properties` isn't a mock but the **real instance**. Which is hard to determine with your dumbed down pseudo code. – M. Deinum Jun 01 '22 at 09:34
  • @M.Deinum Believe me or not, but the value is null. The application doesn't break because it's a unit test, so I suppose it's not initializing all the beans that depend on those properties – Christian Vincenzo Traina Jun 01 '22 at 09:59
  • Trust me a value cannot be `null` else the application won't start and your tests won't run. So you are doing someting in your test that you aren't telling with that code (which are just snippets). – M. Deinum Jun 01 '22 at 10:00
  • @M.Deinum I gave a double check, the Properties are accessed statically. May that be the problem? – Christian Vincenzo Traina Jun 01 '22 at 10:01
  • `@Value` and `@Autowired` don't work for `static` fields. So again this isn't the actual code you are using but a dumbed down version... – M. Deinum Jun 01 '22 at 10:03
  • If staticness is the issue - maybe don't do that? Your property class is already a component you can autowire wherever you need, the static "layer" in it gives you nothing except making testing it's usage harder – Deltharis Jun 01 '22 at 10:06
  • @Deltharis that's a piece of code of a huge project that has been run in production for years, so I'm supposed to don't touch it :) by the way, just note that the getter is static while the setter is not! – Christian Vincenzo Traina Jun 01 '22 at 10:07
  • `private static String example;` Since it's member of a component, why don't you remove the "static"? – SeanH Jun 01 '22 at 10:10
  • I also add this: although the setter is not static, if I put a breakpoint there I notice that it never gets called. Maybe it's not seen as a dependency? – Christian Vincenzo Traina Jun 01 '22 at 10:10
  • 1
    @SeanH because it's not my code, I'm just writing the tests – Christian Vincenzo Traina Jun 01 '22 at 10:11

2 Answers2

4

After many attempts, finally I found a solution. The problem was caused by using Spring 4 with JUnit 5, and even though it wasn't showing any warning or error, it wasn't able to load the Spring context. In all honesty, I don't know what @SpringBootTest was doing in practice.

The solution was about adding the spring-test-junit5 dependency as stated in this answer, then the following steps:

  • Remove the @SpringBootTest annotation
  • Add @ExtendWith(SpringExtension.class), importing the class from spring-test-junit5
  • Add @Import(Properties.class)

Now the annotations for the test look like this:

@ExtendWith(SpringExtension.class)
@PropertySource("classpath:application-configA.properties")
@TestPropertySource("classpath:application-configA.properties")
@Import(Properties.class)
class FooTest {
0

I recommend using @Before in order to set values in unit tests if it's for a whole class using org.springframework.test.util.ReflectionTestUtils,

However in order to do so you will have to inject Properties instance in test class

@InjectMocks Properties properties;
...
@Before
  public void setUp() {
    ReflectionTestUtils.setField(properties, "example", "something");
  }

alternatively you can use same ReflectionTestUtil in @Test too - I think

update: Your way of Using @TestPropertySource is definitely correct but there might be some conflict on using terminology location and properties, In my opinion I have see code doing,

@TestPropertySource(properties = "classpath:application-configA.properties")

Plus, have you tried adding @ActiveProfile('test') and @AutoConfigureMockMvc ?

Milan Desai
  • 1,228
  • 8
  • 23
  • I think this could work, the main problem is that they are a lot of properties and I wanna handle them with property files – Christian Vincenzo Traina Jun 01 '22 at 10:24
  • that too is possible imo#, you'll need properties file in test resources. I will try to see if I can put an example. – Milan Desai Jun 01 '22 at 10:26
  • updated answer for file usage, @CristianTraìna try terminology properties rather than location in your `@TestPropertySource` – Milan Desai Jun 01 '22 at 10:35
  • No luck, I've also tried these ones: `@ContextConfiguration(classes = MainApplication.class)`, `@ContextConfiguration(classes = Properties.class)`, `@Import(Properties.class)`, and many others, but the setters within `Properties` are never called. I've also tried removing the static methods, using `@Autowired` within the service, but without any change – Christian Vincenzo Traina Jun 01 '22 at 10:42
  • One of the thing that I hate the most about Spring is that it makes you save a lot of time when things work, but that time is immediately wasted when things don't work. I don't even know if the annotations I'm adding are having any effect. How do I debug or inspect an external annotation? – Christian Vincenzo Traina Jun 01 '22 at 10:45
  • PS. I've also tried all the combinations of `InjectMocks`, `Mock`, `Autowired` and `MockBean`, but looks like it's not loading the spring context at all – Christian Vincenzo Traina Jun 01 '22 at 10:49
  • feel your pain. try @TestPropertySource(properties = {"spring.config.location=classpath:application-configA.properties"}) – Milan Desai Jun 01 '22 at 10:51
  • 1
    I just posted an answer to my own question, the problem was apparently elsewhere. Thank you for your patience and your help! – Christian Vincenzo Traina Jun 03 '22 at 08:03
  • Glad you found the problem – Milan Desai Jun 03 '22 at 20:39