2

I have a RestController that I want to test:

import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class PetController implements PetApi {
    
    @Autowired
    PetRepository pr;
    
    @Override
    public ResponseEntity<Pet> addPet(@Valid Pet pet) {
        pr.save(new PetBE(9L, "dummy"));
        return new ResponseEntity<Pet>(pet, HttpStatus.OK);
    }

}
import org.springframework.data.repository.CrudRepository;

public interface PetRepository extends CrudRepository<PetBE, Long> {
}

I want to mock PetRepository and test if the object passed is the object returned:

import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

import com.example.petstore.backend.api.model.Pet;
import com.example.petstore.backend.db.PetRepository;
import com.example.petstore.backend.db.PetBE;

import static org.mockito.Mockito.when;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.AdditionalAnswers.returnsFirstArg;

@SpringBootTest
public class PetControllerTest {

    @InjectMocks
    private PetController petController;

    @MockBean
    private PetRepository pr;   

    @Test
    void testAddPet() {
        when(pr.save(any(PetBE.class))).then(returnsFirstArg());
        Pet p1 = new Pet().id(5L).name("Klaus");
        assertNotNull(petController);
/*L35*/ ResponseEntity<Pet> r = petController.addPet(p1);
        assertEquals(new ResponseEntity<Pet>(p1, HttpStatus.OK), r);
    }
}

When I run this method as a gradle test, I get

com.example.petstore.backend.api.implementation.PetControllerTest > testAddPet() FAILED
    java.lang.NullPointerException at PetControllerTest.java:35

which is petController.addPet(p1);.

My printlns in addPet are not displayed and I can't set any breakpoints there because it is mocked. The only reference in addPet that could be null is pr, but it works fine when I send a request with curl.
I've also tried adding

    @BeforeAll
    public void setup() {
        MockitoAnnotations.initMocks(this);
    }

because it was suggested here but that gave an InitializationException:

com.example.petstore.backend.api.implementation.PetControllerTest > initializationError FAILED
    org.junit.platform.commons.JUnitException at LifecycleMethodUtils.java:57

How can I debug this?
How can I get this to work?

peer
  • 4,171
  • 8
  • 42
  • 73

1 Answers1

2

You're mixing annotations from various testing frameworks here. If you wish to use the Mockito annotation @InjectMocks then I'd recommend not using any Spring-related mocking annotations at all, but rather the @Mock annotation to create a mocked version of the bean you want to inject (into the @InjectMocks-annotated field). Also make sure you bootstrap the Mockito extension with @ExtendWith(MockitoExtension.class). Something like:

import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

import com.example.petstore.backend.api.model.Pet;
import com.example.petstore.backend.db.PetRepository;
import com.example.petstore.backend.db.PetBE;

import static org.mockito.Mockito.when;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.AdditionalAnswers.returnsFirstArg;

@ExtendWith(MockitoExtension.class)
public class PetControllerTest {

    @InjectMocks
    private PetController petController;

    @Mock
    private PetRepository pr;

    @Test
    void testAddPet() {
        when(pr.save(any(PetBE.class))).then(returnsFirstArg());
        Pet p1 = new Pet().id(5L).name("Klaus");
        assertNotNull(petController);
        ResponseEntity<Pet> r = petController.addPet(p1);
        assertEquals(new ResponseEntity<Pet>(p1, HttpStatus.OK), r);
    }
}

EDIT: Calling MockitoAnnotations.initMocks(this) inside for example a @BeforeEach-annotated method is necessary if you don't want to use the MockitoExtension. They're essentially the same thing, but it's less necessary in JUnit Jupiter because you can extend a test class with multiple extensions, which was not possible in JUnit 4.x. So if you wanted to bootstrap your test with both a Spring context and Mockito, then you had to pick one of them and setup the other one yourself.

Thomas Kåsene
  • 5,301
  • 3
  • 18
  • 30