简体   繁体   中英

@Repository instance is null when invoking a method using a @Service instance from a unit test

My goal is to use an in-memory database for these unit tests, and those dependancies are listed as:

implementation("org.springframework.boot:spring-boot-starter-data-jpa")
runtimeOnly("com.h2database:h2")

So that the repository instance actually interacts with a DB, and I dont just mock return values.

The problem is that when I run my unit test, the repository instance inside the service instance is null .

Why is that? Am I missing some annotation on the unit test class to initialise the repository instance?

This is the console output when running my unit test:

null

java.lang.NullPointerException
    at com.my.MyService.findAll(MyService.java:20)
    at com.my.MyTest.testMy(MyTest.java:23)

My unit test class:

public class MyTest {

  @MockBean
  MyRepository myRepository;

  @Test
  void testMy() {
    MyService myService = new MyService();
    int size = myService.findAll().size();
    Assertions.assertEquals(0, size);
  }
}

My service class:

@Service
public class MyService {

    @Autowired
    MyRepository myRepository;

    public List<MyEntity> findAll() {

        System.out.println(myRepository); // null
        return (List<MyEntity>) myRepository.findAll(); // throws NullPointerException
    }

    @Transactional
    public MyEntity create(MyEntity myEntity) {

        myRepository.save(myEntity);

        return myEntity;
    }
}

My repository class:

@Repository
public interface MyRepository extends CrudRepository<MyEntity, Long> {

}

My entity class:

@Entity
public class MyEntity {

    @Id
    @GeneratedValue
    public Long id;
}

Why is that? Am I missing some annotation on the unit test class to initialise the repository instance?

Basically yes:)

You need to initialise a Spring Context by Annotating your Testclass with @SpringBootTest

The other Problem you have is that you create your MyService Object manually. By doing so SpringBoot has no chance to inject any Bean for you. You can fix this by simply injecting your MyService in your Testclass. Your Code should look something like this:

@SpringBootTest
public class MyTest {

    @Autowired
    private MyService myService;

    @Test
    void testMy() {
        int size = myService.findAll().size();
        assertEquals(0, size);
    }
}

To use @MockBean annotation, you have to use SpringRunner to run the test. Use @RunWith Annotation on top of your test class and pass SpringRunner.class .

@RunWith(SpringRunner.class)
public class MyTest {

  @MockBean
  MyRepository myRepository;

  @Test
  void testMy() {
    MyService myService = new MyService();
    int size = myService.findAll().size();
    Assertions.assertEquals(0, size);
  }
}

I believe you wish to write an integration test. Here you could remove the MockBean annotation and simply autowire your repository. Also, run with The SpringRunner class.

@RunWith(SpringRunner.class)
public class MyTest {

  @Autowired
  MyRepository myRepository;

  @Autowired
  MyService myService
  
  @Test
  void testMy() {
   int size = myService.findAll().size();
   Assertions.assertEquals(0, size);
  }
}

This should work

The problem here is your service implementation. Using @Autowired to inject the dependency will work when you run the whole app, but it do not allow you to inject a custom dependency when you'll need it, and a good example of this is testing.

Change your service implementation to:

@Service
public class MyService {

    private MyRepository myRepository;

    public MyService(MyRepository myRepository){
        this.myRepository = myRepository;
    }

    public List<MyEntity> findAll() {

        System.out.println(myRepository); // null
        return (List<MyEntity>) myRepository.findAll(); // throws NullPointerException
    }

    @Transactional
    public MyEntity create(MyEntity myEntity) {

        myRepository.save(myEntity);

        return myEntity;
    }
}

This constructor will be called by spring. Then change your test to:

public class MyTest {

  @Mock
  MyRepository myRepository;

  @Test
  void testMy() {
    MyService myService = new MyService(myRepository);
    int size = myService.findAll().size();
    Assertions.assertEquals(0, size);
  }
}

Note I have replaced @MockBean to @Mock as the previous annotation is for injecting a mock bean into the spring context, which is not needed if you're doing unit testing. If you want to boot spring context (which I would not recommend you) you need to configure your test class with @SpringBootTest or some of the other available alternatives. That will convert your test into an integration test.

PD: This test will not work if you don't provide a mock to myRepository.findAll() . Mockito default behaviour is to return null, but you're expecting it to return 0, so you'll need to do something like given(myRepository.findAll()).willReturn(0) .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM