Testing Spring Hateoas Application with RepresentationModelAssembler - java

I'm trying to test my Spring Hateoas application, more specifically the controllers, using Springs #WebMvcTest. But I'm having problems injecting my custom RepresentationModelAssembler into the test.
First a bit of my setup:
I'm using a custom RepresentationModelAssembler to turn my DB-Models into DTOs, which have all necessary links added.
The RepresentationModelAssembler:
#Component
public class BusinessUnitAssembler implements RepresentationModelAssembler<BusinessUnit, BusinessUnitDto> {
private final Class<BusinessUnitController> controllerClass = BusinessUnitController.class;
private final BusinessUnitMapper businessUnitMapper;
public BusinessUnitAssembler(BusinessUnitMapper businessUnitMapper) {
this.businessUnitMapper = businessUnitMapper;
}
#Override
public BusinessUnitDto toModel(BusinessUnit entity) {
return businessUnitMapper.businessUnitToDto(entity)
.add(linkTo(methodOn(controllerClass).findById(entity.getId())).withSelfRel());
}
}
The BusinessUnitMapper used here is a Mapstruct mapper, which is injected by spring. In my Service I use the BusinessUnitAssembler to turn my DB-Models into DTOs, example Service method:
public Page<BusinessUnitDto> findAll(Pageable pageable) {
Page<BusinessUnit> pagedResult = businessUnitRepository.findAll(pageable);
if (pagedResult.hasContent()) {
return pagedResult.map(businessUnitAssembler::toModel);
} else {
return Page.empty();
}
}
This is how I'm doing the testing currently:
#WebMvcTest(controllers = BusinessUnitController.class)
public class BusinessUnitControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private BusinessUnitService businessUnitService;
private BusinessUnitMapper mapper = Mappers.getMapper(BusinessUnitMapper.class);
private BusinessUnitAssembler assembler = new BusinessUnitAssembler(mapper);
#Test
public void getAllShouldReturnAllBusinessUnits() throws Exception {
List<BusinessUnitDto> businessUnits = Stream.of(
new BusinessUnit(1L, "Personal"),
new BusinessUnit(2L, "IT")
).map(businessUnit -> assembler.toModel(businessUnit)).collect(Collectors.toList());
when(businessUnitService.findAll(Pageable.ofSize(10))).thenReturn(new PageImpl<>(businessUnits));
mockMvc.perform(get("/businessUnits").accept(MediaTypes.HAL_JSON))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.*", hasSize(3)))
// ... do more jsonPath checking
}
}
But I'd like to have Spring inject the BusinessUnitAssembler, instead of constructing it myself. I've tried #Importing BusinessUnitAssembler as well as the BusinessUnitMapper and I've also tried it by using a custom #Configuration but I just couldn't get it to work.
So my Question is: How can I let Spring inject my BusinessUnitAssembler into the test for me instead of assembling it myself?
Additional Question: Is it valid to combine the Mapping from Database Entity to DTO in the RepresentationModelAssembler or should those two steps be kept seperate from each other?

Related

How to mock Page with content data in Unit Test?

How to return Page content in Spring Boot Unit test service layer? How to mock this data with some values and later on test it?
Service that needs to be tested:
#Service
#RequiredArgsConstructor
#Transactional(readOnly = true)
public class CampaignReadServiceImpl02 {
private final CampaignRepository campaignRepository;
public Page<Campaign> getAll(int page, int size) {
Pageable pageable = PageRequest.of(page, size);
Page<Campaign> pages = campaignRepository.findAll(pageable);
return pages;
}
}
The class that mocks data in Unit test
#Slf4j
#ExtendWith(MockitoExtension.class)
public class CampaignReadServiceTest {
#Mock
private CampaignRepository campaignRepository;
private CampaignReadServiceImpl02 campaignReadServiceImpl02;
#BeforeEach
public void beforeEach() {
campaignReadServiceImpl02 = new CampaignReadServiceImpl02(campaignRepository);
}
#Test
public void testGetAll02() {
log.info("Testing get all campaigns method");
//this need to have content data inside of page.getContent(), need to be added
Page<Campaign> page = Mockito.mock(Page.class);
Mockito.when(campaignRepository.findAll(Mockito.any(Pageable.class))).thenReturn(page);
Page<Campaign> result = campaignReadServiceImpl02.getAll(2, 2);
Assertions.assertNotNull(result);
Mockito.verify(campaignRepository, Mockito.times(1)).findAll(Mockito.any(Pageable.class));
Mockito.verifyNoMoreInteractions(campaignRepository);
}
}
How to mock Page<Campaign> page = Mockito.mock(Page.class); to get result.getContent(); when Service repository is injected in Service..
I can't test result.getContent() because I don't have data from repository, maube because I need to change mock Page<Campaign> page with Page.class to something else?
How to properly mock Page<Campaign> page = Mockito.mock(Page.class); that will return some data later on in service: result.getContent().name(), etc..
easiest way would be to create an object instead of mocking the class.
Page<TournamentEntity> tournamentEntitiesPage = new PageImpl<>(List.of(obj1, obj2), pageable, 0);

Spring Validation not working on Service Layer level

I have a Spring Boot project ( 2.3.3 ) where I want to validate the service layer methods input parameters. So in my pom.xml I added
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
as it is no more part of the parent. Next I have my service method interface and the implementing service method. My implemening service is annotated with #Validated and my method looks like
public void deleteGreetingById(#NotNull(message = "greetingId must not be null.")Integer greetingId) {
I've also read that the validation is bound per default only to the controller layer. So to enable it also for the servie layer I added a PostValidationProcesser.
#Configuration
public class MethodValidationConfig {
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
When I now execute my test with null as input param, nothing happens and no exception is thrown. When I do
Assert.notNull(greetingId,"greetingId must not be null");
inside the method, an InvalidParameterException is thrown like expected. But I would prefere the annotation based validation because of the #Valid validation of whole class Objects as input parameter.
Can one explain why the validation is not triggered?
EDIT:
#RestController
public class GreetingsConsumerController {
private final GreetingsService greetingsService;
public GreetingsConsumerController(GreetingsService greetingsService) {
this.greetingsService = greetingsService;
}
#PostMapping(value = "/greetings", consumes = MediaType.APPLICATION_JSON_VALUE)
public Greeting createGreeting( #RequestBody #Valid GreetingDto greetingDto){
return greetingsService.addGreeting(greetingDto);
}
#GetMapping(value = "/greetings/{id}")
public Greeting getGreetingById(#PathVariable Integer id){
return greetingsService.findGreetingById(id);
}
#GetMapping(value = "/greetings")
public List<Greeting> getAllGreetings(){
return greetingsService.findAllGreetings();
}
#DeleteMapping(value = "/greetings/{id}")
#ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteGreetingById(#PathVariable Integer id){
greetingsService.deleteGreetingById(id);
}
}
Interface:
public interface GreetingsService {
Greeting findGreetingById(Integer greetingId);
List<Greeting> findAllGreetings();
Greeting addGreeting( GreetingDto greetingDto);
void deleteGreetingById( Integer greetingId);
}
IterfaceImpl:
#Service
#Validated
public class GreetingsServiceImpl implements GreetingsService {
.
.
.
#Override
public void deleteGreetingById(#NotNull(message = "greetingId must not be null. ") Integer greetingId) {
...
}
}
I also added the Bean to my SpringBootApplication but still no exception is thrown.
#SpringBootApplication
public class GreetingsConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(GreetingsConsumerApplication.class, args
);
}
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
Below is the sample example to validate a model at service layer.
class TestModel{
#NotNull
private String name;
}
TestModel model= new TestModel();
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<TestModel>> violations = validator.validate(model);
I "solved" the problem. My error was that I configured my Tests wrong. I configured the test with
#Extendwith(SpringExtension.class)
as I've only written unit tests without using the context in this class before. Obviously using the parameter validation this way you have to use the Context which makes the whole scenario an integration test. I'm glad it works now and I'm sorry for the needless discussions. I should have posted my test also in the code.
Although I am glad it works now I'm also a bit confused. In genereal I don't want to start the Spring context just for constraint validation. But this is another question.
When you have services implementing interfaces and you reference the interface you need the validation annotations on the interface, not the implementing class. Add the validation annotations to the GreetingsService interface.

Injecting an object that uses builder pattern inside spring constructor

I've seen this question asked a lot on here, but none of the answers have helped me in my situation.
I'm using square sdk which prompts me to create the client like this, before using it.
import com.squareup.square.SquareClient;
import com.squareup.square.Environment;
SquareClient square = new SquareClient.Builder()
.environment(Environment.SANDBOX)
.accessToken("YOUR_SANDBOX_ACCESS_TOKEN")
.build();
Given the service looks something like this, spring will not able to find the required bean. And will give an error like,
No qualifying bean of type 'squareClient'
So the question I have is how to load the two paramters into the service using dependecy injection in order to make square service testable.
#Service
public class SquareService {
private final SquareClient squareClient;
public SquareService(SquareClient squareClient) {
this.squareClient = squareClient;
}
}
And my test I have
#Before
public void setup() {
sut = new SquareService(new SquareClient.Builder()
.environment(Environment.SANDBOX)
.accessToken("YOUR_SANDBOX_ACCESS_TOKEN")
.build());
}
You can create a #Configuration class and create the bean manually:
#Configuration
public class SquareConfiguration {
#Value("${my.config.sandboxAccessToken}")
private String sandboxAccessToken;
#Bean
public SquareService createSquareService(){
return new SquareService(new SquareClient.Builder()
.environment(Environment.SANDBOX)
.accessToken(this.sandboxAccessToken)
.build());
}
}

How to test a Spring REST consumer with JUnit

I'm creating a Java Spring based microservice application that communicates using REST endpoints.
The app itself is so far has a simple structure: UI <-> DBLayer. The UI module is a api consumer and DBLayer is an api provider.
That being said I would like to test if my UI makes the correct REST calls using JUnit and/or Mockito. To be more specific, say I have a service class like this:
#Service
public class AuthorityService {
#Autowired
private RestTemplate restTemplate;
public Authority getAuthority(String authorityName) {
Authority authority =
restTemplate.getForObject(
"http://localhost:8080/authorities/" + authorityName,
Authority.class);
return authority;
}
}
In order to test this service method I would like to somehow verify that exactly this endpoint was called. Is there a way to wrap the service method and somehow assert a rest GET/POST/PUT etc. calls being made?
The desired test class should look something like this:
public class AuthorityServiceTest {
private AuthorityService authorityService = new AuthorityService();
#Test
public void getAuthorityTest(){
Assert.assertHttpGETCallMade(
authorityService.getAuthority("test"),
"http://localhost:8080/authorities/test");
}
}
You can use Mockito to inject the template, then verify the call.
#ExtendWith(MockitoExtension.class) // RunWith(MockitoJUnitRunner.class) for JUnit 4
public class AuthorityServiceTest {
#InjectMocks
private AuthorityService sut;
#Mock RestTemplate restTemplate;
#Test
public void getAuthorityTest(){
// mock rest call
Authority auth = mock(Authority.class);
when(restTemplate.getForObject(any(String.class), any(Class.class)).thenReturn(auth);
Authority result = sut.getAuthority("test");
// verify mock result was returned
assertSame(auth, result);
// verify call to rest template was performed
verify(restTemplate).getForObject(
"http://localhost:8080/authorities/test",
Authority.class);
}
}

spring-mvc: how to test Rx responses with mockMvc?

my controller is:
import rx.Single;
...
#GetMapping
Single<List<MyType>> fetchFromDB() {
return Single
.fromCallable(() -> dao.fetch())
.subscribeOn(Schedulers.io());
}
and it works perfectly. but i can't tests it. i tried:
MvcResult asyncResult = mvc.perform(get("/")).andReturn()
String result = mvc
.perform(asyncDispatch(asyncResult))
.andReturn().getResponse().getContentAsString()
but it fails with:
java.lang.IllegalStateException: Async result for handler [rx.Single<java.util.List<MyType>> MyController.fetchFromDB()] was not set during the specified timeToWait=-1
at org.springframework.test.web.servlet.DefaultMvcResult.getAsyncResult(DefaultMvcResult.java:145)
at org.springframework.test.web.servlet.DefaultMvcResult.getAsyncResult(DefaultMvcResult.java:121)
at org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch(MockMvcRequestBuilders.java:246)
at MyControllerSpec.should fetch from db...
so: how to test rx.Single with spring mvc?
i found the answer. when you create your mockMvc object, add a handler for Single:
return MockMvcBuilders.standaloneSetup(controller)
.setCustomReturnValueHandlers(new SingleReturnValueHandler())
if you use MockMvcBuilders.webAppContextSetup(context) instead, you can add the handler in your WebMvcConfigurerAdapter:
public class Config extends WebMvcConfigurerAdapter {
#Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
returnValueHandlers.add(new SingleReturnValueHandler());
}
}

Categories

Resources