14

I have a service class that I need to unit test. The service has a upload method which in turn calls other services(autowired beans) that updates the database. I need to mock some of these services and some to execute as it is.

@Service
public class UploadServiceImpl implements UploadService{
  @Autowired
  private ServiceA serviceA;

  @Autowired
  private ServiceB serviceB;

  public void upload(){
    serviceA.execute();
    serviceB.execute():

    //code...
}

In the above example I need to mock ServiceA, but i would like ServiceB to run as is and perform it's function. My Junit test looks like this:

@RunWith(SpringJUnit4ClassRunner.class) 
@SpringBootTest(classes=Swagger2SpringBoot.class) 
public class UploadServiceTest {
  @Mock
  private ServiceA serviceA;

  @InjectMocks
  private UploadServiceImpl uploadService;

  @Before
  public void init() {
    MockitoAnnotations.initMocks(this);
  }

  @Test
  public void testUpload(){
    uploadService.upload();

  }

When I execute this I get NPE at serviceB.execute(); in UploadServiceImpl.

What could be the problem?

Note: I am not specifying the behavior of the mocked object because I don't really care and also default behavior of mocked objects are to do nothing.

Thanks!

Pavneet_Singh
  • 36,884
  • 5
  • 53
  • 68
ronak_11
  • 245
  • 1
  • 3
  • 12

7 Answers7

14

Usually when unit testing you want to mock all external dependencies of a class. That way the unit test can remain independent and focused on the class under test.

Nevertheless, if you want to mix Spring autowiring with Mockito mocks, an easy solution is to annotate with both @InjectMocks and @Autowired:

  @InjectMocks
  @Autowired
  private UploadServiceImpl uploadService;

The net effect of this is that first Spring will autowire the bean, then Mockito will immediately overwrite the mocked dependencies with the available mocks.

rustyx
  • 80,671
  • 25
  • 200
  • 267
3

The issue you are facing is due to the use of @InjectMocks annotation.@InjectMocks marks a field on which injection should be performed. Mockito will try to inject mocks only either by constructor injection, setter injection, or property injection – in this order. If any of the given injection strategy fail, then Mockito won’t report failure.

So in your case when trying to inject mocks only one mock bean is present and the other bean ServiceA is not getting injected.To solve this issue :

You can try not using @InjectMocks at all instead pass a mock object for the method that you want to mock while pass rest of the autowired objects into the constructor.Example :

Here to test i am passing one mock object and one autowired object.

@RunWith(MockitoJUnitRunner.class)
public class SampleTestServiceImplTest {

@Mock
private SampleClient sampleClient;
@Autowired
private BackendService backendService ;

private BackendServiceImpl backendServiceimpl;

@Before
void setUp() {
    backendServiceimpl = new BackendServiceImpl(sampleClient, backendService);
}

Or another way you can make this work is by using @Autowired annotation along with the @InjectMocks.@Autowired @InjectMocks are used together and what it will do is inject the mocked class and Autowired annotation adds any other dependency which the class might have.

Answer referred from : https://medium.com/@vatsalsinghal/autowired-and-injectmocks-in-tandem-a424517fdd29

Ananthapadmanabhan
  • 5,706
  • 6
  • 22
  • 39
1

In my opinion, we are writing unit test cases and we should not initialize the spring context in order to test a piece of code.

So,

I used Mockito to mock the Autowired beans in my main target test class and injected those mock beans in my main test class Object

maybe sounds confusing, see the following example

Dependencies I used

    testImplementation("org.mockito:mockito-core:2.28.2")
    testImplementation("org.mockito:mockito-inline:2.13.0")
    testImplementation("org.junit.jupiter:junit-jupiter:5.8.2")
    testImplementation("org.mockito:mockito-junit-jupiter:4.0.0")

My main class is Maths and Calculator bean is autowired


class Maths{

   @Autowired Calculator cal;

   .........
   .........

   public void randomAddMethod(){
      cal.addTwoNumbers(1,2); // will return 3;
   }
}

Test class


@ExtendWith(MockitoExtension.class)

class MathsTest{

   @Mock(answer = Answers.RETURNS_DEEP_STUBS) Calculator cal;

   @InjectMocks Maths maths = new Maths();

   @Test testMethodToCheckCalObjectIsNotNull(){
      maths.randomAddMethod();
   }
}

Now cal will not be null in Maths class and will work as expected

Dupinder Singh
  • 7,175
  • 6
  • 37
  • 61
0

Add

  @Mock
  private ServiceB serviceB;

to create injectable mock of missing service just like you did with service A.

Antoniossss
  • 31,590
  • 6
  • 57
  • 99
0

In my case, beside using combination of @InjectMocks and @Autowired, I also had to provide setter for the mocked object in the tested class (setter for ServiceA in UploadServiceImpl in the original example). Without that the real method of ServiceA was called.

mitrue
  • 63
  • 6
0

Another way is to define an autowired constructor so that you can test the services properly.

@Service
public class UploadServiceImpl implements UploadService{

  private ServiceA serviceA;
  private ServiceB serviceB;

  @Autowired
  public UploadServiceImpl(ServiceA serviceA, ServiceB serviceB) {
    this.serviceA = serviceA;
    this.serviceB = serviceB;
  }

  public void upload(){
    serviceA.execute();
    serviceB.execute():

    //code...
}
dvallejo
  • 1,033
  • 11
  • 25
0

I couldn't make it work without using ReflectionTestUtils. Setting the constructor is one option if it's viable for you.

Smart Coder
  • 1,435
  • 19
  • 19