Issue
I use Spring Boot 5 and JUnit in my project. I create a unit test to test the service.
Here is the service that I am testing:
@Service
@RequiredArgsConstructor
@Slf4j
public class BuilderServiceImpl implements BuilderService{
@Autowired
public AutoMapper autoMapper;
private final BuilderRepository builderRepository;
private final AdminUserRepository adminUserRepository;
@Override
public BuilderDto getByEmail(String email){
}
@Override
public List<BuilderMinDto> getAll() {}
@Override
public List<BuilderMinDto> getAll(int page, int size) {}
@Override
public SaveBuilderResponse create(Builder builder){
var str = autoMapper.getDummyText();
Builder savedBuilder = builderRepository.save(builder);
return new SaveBuilderResponse(savedBuilder);
}
}
And here is the test class that tests the service above:
@SpringBootTest
@RequiredArgsConstructor
@Slf4j
class BuilderServiceImplTest {
@Mock
private BuilderRepository builderRepository;
@Mock
private AdminUserRepository adminUserRepository;
private AutoCloseable autoCloseable;
private BuilderService underTest;
@BeforeEach
void setUp(){
autoCloseable = MockitoAnnotations.openMocks(this);
underTest = new BuilderServiceImpl(builderRepository,adminUserRepository);
}
@AfterEach
void tearDown () throws Exception{
autoCloseable.close();
}
@Test
void getByEmail(){}
@Test
@Disabled
void getAll() { }
@Test
@Disabled
void testGetAll() {}
@Test
void create() {
//given
Builder builder = new Builder();
builder.setName("John Johnson");
builder.setCompanyName("Builders Test");
builder.setEmail("[email protected]");
//when
underTest.create(builder);
//then
ArgumentCaptor<Builder> builderArgumentCaptor = ArgumentCaptor.forClass(Builder.class);
verify(builderRepository)
.save(builderArgumentCaptor.capture());
Builder captureBuilder = builderArgumentCaptor.getValue();
assertThat(captureBuilder).isEqualTo(builder);
}
}
When I start to run the test class the create method in BuilderServiceImpl fired and on this row:
var str = autoMapper.getDummyText();
I get NullPointerException(autoMapper instance is null).
Here is the definition of AutoMapper class:
@Component
@Slf4j
@RequiredArgsConstructor
public class AutoMapper {
public String getDummyText(){
return "Hello From AutoMapper.";
}
}
As you can see I use @Component annotation to register the AutoMapper class to the IoC container and Autowired annotation to inject it into autoMapper property in BuilderServiceImpl class.
Why autoMapper instance is null? How can I make autoMapper to be initialized?
Solution
In order to make @Autowire
work you have to use the instance of BuilderServiceImpl
(object under test) created by spring itself.
When you create the object like this (by yourself, manually):
@BeforeEach
void setUp(){
....
underTest = new BuilderServiceImpl(builderRepository,adminUserRepository);
}
Spring doesn't know anything about this object, hence Autowiring won't work
Another thing that might be useful:
You've used @Mock
for BuilderRepository
and AdminUserRepository
.
This are plain mockito annotation, and if you're using an integration/system test that runs the spring under the hood, probably this is not what you want:
Surely, it will create a mock, but it won't put it onto an application context, and won't substitute the beans of these classes that might have been created by spring.
So if this is what you want to achieve, you should use @MockBean
instead.
This annotation belongs to Spring Testing framework rather than a plain mockito annotation.
All-in-all you might end up with something like this:
@SpringBootTest
class MyTest
{
@MockBean
BuilderRepository builderRepo;
@MockBean
AdminUserRepository adminUserRepo;
@Autowired // spring will inject your mock repository implementations
// automatically
BuilderServiceImpl underTest;
@Test
void mytest() {
...
}
}
Answered By - Mark Bramnik
Answer Checked By - David Goodson (JavaFixing Volunteer)