Reputation: 1133
I am developing a rest api using spring boot . It consists of the standard layers : controller ( @RestController) ( handles incoming http requests and exposes api endpoint ) , then the service layer ( @Service ) and finally the Repository layer ( @Repository )
My question is around unit tests.
To test my controller - I am mocking the call to service layer using mockito. Also to prevent unnecessary loading of entire context had read a little about 'spring slices' So to test my controller - this was the annotation used:
@WebMvcTest
@ExtendWith(SpringExtension.class)
@WebMvcTest(controllers = TieredClaimController.class)
class TieredClaimControllerTest {
@MockBean
private TieredClaimService tieredClaimService;
@Autowired
private MockMvc mockMvc;
Similarly to test my spring data repository used another spring slice annotation : @DataJpaTest
@ExtendWith(SpringExtension.class)
@DataJpaTest
@ContextConfiguration(initializers = {SalesRepositoryTest.Initializer.class})
public class SalesRepositoryTest {
@Autowired
private SalesRepository repository;
So I can see dedicated spring slice annotations for the Web side of things ( @WebMvcTest ) and for database side of things ( @DataJpaTest )
However when I need to test my @Service annotated classes which spring slice do I use ? I dont see any dedicated for the service layer
The reason I ask is that I am using it this way : NOTE ( the call to repository is mocked so essentially my service layer unit test is isolated )
@ExtendWith(SpringExtension.class)
@SpringBootTest
class TieredClaimServiceTest {
@Autowired
private TieredClaimService tieredClaimService;
@MockBean
private SalesRepository salesRepository;
However the problem is - that when I run these unit tests there is some unnecessary jpa / hibernate code is getting called .
How do I prevent this ?
2019-11-29 | 21:04:17.293 | SpringContextShutdownHook | DEBUG | org.hibernate.SQL | drop table discount_tiers if exists Hibernate: drop table discount_tiers if exists 2019-11-29 | 21:04:17.293 | SpringContextShutdownHook | DEBUG | org.hibernate.SQL | drop table merch if exists Hibernate: drop table merch if exists 2019-11-29 | 21:04:17.293 | SpringContextShutdownHook | DEBUG | org.hibernate.SQL | drop table sales if exists Hibernate: drop table sales if exists 2019-11-29 | 21:04:17.293 | SpringContextShutdownHook | DEBUG | org.hibernate.SQL | drop table user if exists Hibernate: drop table user if exists 2019-11-29 | 21:04:17.309 | SpringContextShutdownHook | DEBUG | org.hibernate.SQL | drop sequence if exists hibernate_sequence Hibernate: drop sequence if exists hibernate_sequence*
2019-11-29 | 21:04:17.309 | SpringContextShutdownHook | DEBUG | o.h.t.s.TypeConfiguration$Scope | Un-scoping TypeConfiguration [org.hibernate.type.spi.TypeConfiguration$Scope@89296ce] from SessionFactory [org.hibernate.internal.SessionFactoryImpl@17fddecd]
I guess these show up in the logs since in my application.properties ( under /src/test/resources ) I have this:
spring.jpa.hibernate.ddl-auto=create-drop
However when I unit test the repository layer I would need this configuration so I cannot delete or remove it
so for my service layer are there any spring slice annotations ? How do I avoid hibernate / jpa calls or loading to happen when I am testing a service layer which is isolated from database / repository since that layer is mocked ?
EDIT1: Based on answer below I guess I did not provide the complete details of my service class : I tried the following but since one more service class is being injected I am running into issues : ( DiscountTierService is also injected into TieredClaimServiceImpl )
Here is the complete example :
@Service
public class TieredClaimServiceImpl implements TieredClaimService {
//@Autowired
private MerchRepository merchRepository;
//@Autowired
private SalesRepository salesRepository;
@Autowired
private DiscountTierService discountTierService;
private static final Logger LOGGER = LoggerFactory.getLogger(TieredClaimServiceImpl.class);
public TieredClaimServiceImpl() {
}
@Autowired
public TieredClaimServiceImpl(MerchRepository merchRepository,SalesRepository salesRepository) {
this.merchRepository = merchRepository;
this.salesRepository = salesRepository;
}
@Override
//public List<? extends MerchSales> calculateClaim(String code,LocalDate fromDate,LocalDate toDate) {
public List <TieredClaimDto> calculateClaim(ClaimRequestDto claimRequestDto,String xAppCorelationId) throws SystemException {
And here is the revised test class :
@ExtendWith(SpringExtension.class)
class TieredClaimServiceTest {
private TieredClaimService tieredClaimService;
@MockBean
private SalesRepository salesRepository;
@MockBean
private MerchRepository merchRepository;
@BeforeEach
void setUp() {
tieredClaimService = new
TieredClaimServiceImpl(merchRepository,salesRepository);
}
//@Autowired
//private DiscountTierService discountTierService;
@ParameterizedTest
@ValueSource(strings = {"merch", "sales"})
@DisplayName("xyz ")
void tieredClaimPositiveScenarioWithinTier(String sourceType) throws Exception {
In the actual service class I am getting an injection since the DiscountTierService is not getting injected :
I get a NPE at the following line of code :
@Override
public List <TieredClaimDto> calculateClaim(ClaimRequestDto claimRequestDto,String xAppCorelationId) throws SystemException {
/** get the discount tier config data **/
**List<DiscountTierDto> discountTierList = discountTierService.get();**
Upvotes: 5
Views: 2278
Reputation: 104
For your service-layer you don't use/need a spring slice. You test your service.class simply with JUnit and mocks the repository with Mockito like you did it in your controller with the service.
You can also annotate you testclass with @RunWith(MockitoJUnitRunner.class) or @RunWith(SpringRunner.class), if you need a SpringContext...
Upvotes: 3