4

I have configuration class:

@ConfigurationProperties(prefix = "myConfig")
public class MyConfig {
    protected String config;
}

My service uses this config class (That gets the value from application.yml):

@Service
public class myService {

    @Inject
    private MyConfig myConfig;

    Public String getInfo (String param) {
        if (isEmpty(param)) { throw new InvalidParameterException; }
        return myConfig.getConfig();
    }
}

I'm trying to test it with Mockito:

@RunWith(MockitoJUnitRunner.class)
public class myTest {

    @InjectMocks
    private MyService myService;

    @Mock
    private MyConfig myConfig;

    @Test
    public void myTest1() {
        myService.getInfo("a");
    }

    @Test
    public void myTest2() {
        assertThrows(InvalidParameterException.class, ()->myService.getInfo(null));
    }
}

myTest fails since the config class is mocked, thus has null values. What is the right way to test configuration class with Mockito?

Edit: I have a number of config classes like the one above that are being used in myService.

John Ellis
  • 173
  • 2
  • 2
  • 7
  • You have to define the behaviour of your `myConfig` mock. For example: `when(myConfig.getConfig()).thenReturn( ... );` where `...` is whatever you want to return. However you need a method in the MyConfig object if you want to define behaviour. You also might want to add some code to your question so we can see what `MyService` is actually doing. – second Aug 01 '19 at 08:01
  • You might want to take a look at this question: https://stackoverflow.com/questions/31745168/how-to-test-classes-with-configurationproperties-and-autowired – second Aug 01 '19 at 08:25

3 Answers3

2

You need to create getter which then can be mocked by Mockito.

@ConfigurationProperties(prefix = "myConfig")
public class MyConfig {
    protected String config;

    public String getConfig() {
        return config;
    }
}

.

@RunWith(MockitoJUnitRunner.class)
public class myTest {

    @InjectMocks
    private MyService myService;

    @Mock
    private MyConfig myConfig;

    @Before
    private void initializeConfig() {
        when(myConfig.getConfig()).thenReturn("someValue");
    }

    @Test
    public void myTest1() {
        myService.getInfo("a");
    }
}

But if you don't want to set the value explicitly in test, you should create a Spring integration test which would create the whole context and use the real objects. But this is out of scope of this question.

mwajcht
  • 71
  • 4
  • 1
    This config class gets the data from my `application.yml`, so I want to use the data from this file and don't want to set it manually in the test. Is there a way to get it to set from the `application.yml`? – John Ellis Aug 01 '19 at 08:14
  • I'm afraid Mockito runner cannot do that. Maybe try to use SpringRunner.class which should create the whole context and inject the dependencies. It is possible that you would also need to create some Context used only in tests. – mwajcht Aug 01 '19 at 08:22
  • How can I use a real object? – John Ellis Aug 01 '19 at 08:24
0

You should mark MyConfig with @Configuration so that Spring creates a Spring bean in the application context.

you also need to use @RunWith(SpringRunner.class)

The SpringRunner provides support for loading a Spring ApplicationContext and having beans @Autowired into your test instance.

Also change @Inject with @Autowired because i am not sure how @inject will be supported by spring

Service Class

@Service
public class myService {

    @Autowired
    private MyConfig myConfig;

    Public String getInfo {
        return myConfig.getConfig();
    }
}

Test class

@RunWith(SpringRunner.class)
public class myTest {

    @Mock
    private MyService myService;

    @InjectMocks
    private MyConfig myConfig;

This is way you should get real object in test

EDIT

Considering you don't want to mock myservice then we should replace your test class with below and let me know

@RunWith(SpringRunner.class)
public class myTest {

@autowired
private MyService myService;

@Test
public void myTest1() {
    myService.getInfo("<NAME_OF_PROPERTIES>");
}

@Test
public void myTest2() {
    assertThrows(InvalidParameterException.class, ()->myService.getInfo(null));
}
psi
  • 269
  • 2
  • 13
  • I'm still getting a NullPointerException with these exceptions. How does that work? Doesn't InjectMocks gives you a mocked object with null values? – John Ellis Aug 01 '19 at 10:24
  • you can even use @autowired on MyConfig in test class and then get that instance setup with myService.setMyConfig(<>) . – psi Aug 01 '19 at 10:31
  • `@InjectMocks` creates a real object and tries to inject the fields annotated with `@Mock`. However in your case you do not want to mock the `MyService` class (as you want to have the real values). – second Aug 01 '19 at 10:34
  • @psi: You switched the annotations in your example. – second Aug 01 '19 at 10:36
  • @second I agree.. i did it just to get real object Myconfig injected to service mock object – psi Aug 01 '19 at 10:37
  • @psi: That makes little sense.A mock doesn't have dependencies. It wouldn't use them anyway. I suggest you change you're example to not use the mocks. `MyService` is the class under test so mocking it is a bad idead. – second Aug 01 '19 at 10:39
  • @second yes even that should get it working. But remember InjectMocks will work with Mock only so there is no point in adding autowired myservice in test class – psi Aug 01 '19 at 10:44
  • So what is the right way to get real objects for both `myService` and `myConfig` for the test? – John Ellis Aug 01 '19 at 10:54
  • Using `Autowired` failes for all tests that worked (that don't use the config class) – John Ellis Aug 01 '19 at 11:11
  • @JohnEllis i think try to revisit complete answer i posted and make sure you followed it completely. – psi Aug 01 '19 at 11:18
  • My other tests are just argument verification for `getInfo` – John Ellis Aug 01 '19 at 11:18
  • @second I added myTest2 as an example – John Ellis Aug 01 '19 at 11:26
  • @JohnEllis just follow EDIT part of my answer and let me know – psi Aug 01 '19 at 11:59
  • `org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name '[...].myservice.myTest': Unsatisfied dependency expressed through field 'myService'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type '[...].myservice.service.myService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}` – John Ellis Aug 01 '19 at 12:06
  • @psi:Looks like the `Autowiring` itself is not properly working yet. There is probably some additional configuration missing, update your example if you can. – second Aug 01 '19 at 17:14
  • @JohnEllis it clearly mentioned No qualifying bean of type '[...].myservice.service.myService' available: expected at least 1 bean which qualifies as autowire candidate. that means you need to check whether you have myservice available so that it will autowire. – psi Aug 02 '19 at 07:38
  • @JohnEllis i have created small project and i am able to get real object of service in test class https://github.com/prashantingole01/SpringTesting.git the solution which i suggested is working fine in this git repo – psi Aug 02 '19 at 10:13
0

I had a similar issue, so I want to provide my solution, maybe will be helpful for someone, I had a test class that uses Mockito to mock some dependency, but I also needed to use a yaml and of course I didn't want to set values manually using "when(myConfig.getConfig()).thenReturn("someValue");"

I simply solved it by using SpringExtension along with MockitoExtension.

@EnableConfigurationProperties
@ContextConfiguration(classes = { MyConfig.class })
@Extensions({
        @ExtendWith(SpringExtension.class),
        @ExtendWith(MockitoExtension.class)
})
class MyTest {

    @Autowired
    private MyConfig myConfig;

}

for more details: How to test Externalized Configuration reading from yaml

Paul Marcelin Bejan
  • 990
  • 2
  • 7
  • 14