0

Problem Summary:

A configuration object is created successfully during regular run (using @Autowired), but is null when running in unit test phase.

Edit, in response to flagging the question as duplicate: I don't think this is a duplication of Why is my Spring @Autowired field null? as my question is about unit test phase and the above question is about regular run phase.

Detailed:

I have a configuration object that reads a properties file:

@Configuration
@ConfigurationProperties(prefix = "defaults")
@Getter
@Setter
public class DefaultsConfigProperties {
   Double value;
   ....

In the service layer, I'm using this class successfully when running the app in regular mode:

@Service
@Configuration   
public class CatsService {

@Autowired
DefaultsConfigProperties defaultsConfigProperties;
...

However, the problem is during Unit test, as the defaultsConfigProperties is null.

Here is the test class:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
@TestPropertySource(properties = {
    "defaults.value=0.2"
})
public class CatServiceTest {
private CatService;

@Before
public void before(){
    catService = new CatService();
}

If I understand correctly, I need to somehow create / inject / mock the DefaultsConfigProperties class during the unit test phase.

I also know that using the @Autowired is not recommended and it is better to pass items to the constructor of the class. So, should the solution be that the CatService will need to accept the DefaultsConfigProperties in the constructor, and then to create it (how?) in the CatServiceTest ?

riorio
  • 6,500
  • 7
  • 47
  • 100
  • You're doing 2 separate things here that lead to an entirely different test scenario. **(1)** Either you're using Spring to create a `DefaultsConfigProperties` with value `0.2` within your test. In this case, the duplicate is still valid because you're self-creating `CatService`. **(2)** Alternatively you're trying to create a dummy `DefaultsConfigProperties` class, but in that case you don't need `@SpringBootTest`, `@TestPropertySource`, ... . Since the duplicate still applies in situation (1), I'm not going to vote to reopen unless you clarify that within your question. – g00glen00b Sep 17 '18 at 11:50

2 Answers2

1

You are on the right track, as you stated constructor injection is favored above field injection.

Nevertheless if you want to rely on field injection you could use Mockito.

Example for usage in a unit test:

@Mock
DefaultsConfigProperties defaultsConfigProperties;

@InjectMocks
private CatService;

@Before
public void before(){
    catService = new CatService();
    MockitoAnnotations.init(this);
}

So in your case you should also remove the annotations of your test class to make it a unit test.

mrkernelpanic
  • 4,268
  • 4
  • 28
  • 52
1

The @SpringBootTest annotation takes a classes array parameter. You can pass any class annotated with @Configuration into this array, and also the class containing your main method (SpringApplication.run(...)) if you want to load the same ApplicationContext. In your case:

@SpringBootTest(classes = {Application.class, DefaultsConfigProperties.class})

Note you can also use SpringRunner directly.

Paul Benn
  • 1,911
  • 11
  • 26