Correctly testing a Spring service

Darrel Holt picture Darrel Holt · Jun 30, 2017 · Viewed 13.6k times · Source

The following test fails with a NullPointerException on the line usersRepo.save(user);. I believe it's because when the test goes into the performProvision() function the usersRepo object is null.

However, when the web service is actually running and the endpoint for my controller is hit, everything works fine and the database is updated.

Any idea why the test fails? My idea was that PAutoProvision references the real database, whereas it should be dealing with the in-memory database so maybe there is some sort of conflict? I have also seen a lot of different examples with annotations set up differently, so I suppose that could be a problem too.

UsersRepo extends JpaRepository where PAutoProvision is a SQL table entity.

If this isn't enough information, I can provide the UsersRepo, PAutoProvision, and ProvisionController classes if necessary.

The Service:

@Service
public class ProvisionService {

    @Autowired
    private UsersRepo usersRepo;


    public String performProvision(UserData userData) {
        try {
            PAutoProvision user = new PAutoProvision(userData);
            usersRepo.save(user);  // OOTB CRUD repository functionality of springData to update/insert record data
            return String.format("Success: User %s has been added to the database", userData.getUserId());
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println(e.toString());
            System.out.println("\n\n Cannot perform the provisioning of the user " + userData.getUserId() + ": \n" + e.toString() + "\n\n");
        }

        return "problem";
    }
}

The Test:

@RunWith(SpringRunner.class)
public class ProvisionServiceTest {

    private ProvisionService provisionService;

    @MockBean
    private UsersRepo usersRepo;

    @Before
    public void setUp(){
        provisionService = new ProvisionService();
    }

     @Test
    public void performProvision_shouldPass() {
        UserData userData = new UserData("userid", 30, 1, "spot", 1);
        try {
            String result = provisionService.performProvision(userData);
            assertThat(result, is(equalTo("Success: User userid has been added to the database")));
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println(e.toString());
        }
    }
}

EDIT 1:

Adding @Autowired and removing setUp() method results in the following:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.autoprovision.ProvisionServiceTest': Unsatisfied dependency expressed through field 'provisionService'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.autoprovision.ProvisionService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

Answer

iBiber picture iBiber · Jun 30, 2017

As JB Nizet already stated, the UserRepo mock is not injected into the provisionService instance, because the provisionService instance is created in the setUp method with new.

Your test should look like this:

@RunWith(SpringRunner.class)
public class ProvisionServiceTest {
    @Autowired // let Spring instantiate the instance to test
    private ProvisionService provisionService;

    @MockBean
    private UsersRepo usersRepo;

    @Test
    public void performProvision_shouldPass() {
        UserData userData = new UserData("userid", 30, 1, "spot", 1);

        String result = provisionService.performProvision(userData);
        assertThat(result, is(equalTo("Success: User userid has been added to the database")));
    }
}