How to mock beans Autowired on service layer in #WebMvcTest - spring-boot

I am testing a REST API's in Spring boot gradle app, my mocked service using #MockBean is returning null. This mocked service return null if there are some beans Autowired in service class(I used constructor injection).
Here is sample Code(Not compiled, only for understanding)
#RestController
#RequestMapping("/xxx")
class TestController {
private RetriveDataService retriveDataService;
public TestControllerx(RetriveDataService retriveDataService) {
this.retriveDataService = retriveDataService;
}
#PostMapping(value = "/yyy")
public MyResponseModel myMethod(#RequestBody MyRequestModel model) {
return retriveDataService.retriveData(model);
}
}
#Service
class RetriveDataService {
private TokenService tokenService;
public RetriveDataService(TokenService tokenService) {
this.tokenService = tokenService;
}
public MyResponseModel retriveData(MyRequestModel model) {
String accessToken = tokenService.getToken().getAccessToken();
return retriveData(model, accessToken);
}
}
#RunWith(SpringRunner.class)
#WebMvcTest(TestController.class)
public class TestControllerTest {
#Autowired
private MockMvc mvc;
#Autowired
private ObjectMapper objectMapper;
#MockBean
private RetriveDataService retriveDataService;
#Test
public void testRetriveData() throws Exception {
mvc.perform(MockMvcRequestBuilders.post("/xxx/yyy").content(objectMapper.writeValueAsString(new MyRequestModel()))
.contentType(MediaType.APPLICATION_JSON_UTF8)).andDo(MockMvcResultHandlers.print())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8));
}
}
When I run this test, i am getting following output(If my service do not need another bean, I am getting expected output)
MockHttpServletResponse:
Status = 200
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
Due to this response i facing problem on line .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8));. also when i check response body(as body is also a null)
Sample project to reproduce the issue is here

Checking your repository confirmed assumption form the discussion in comments under question.
You specify expectations on your mock
MyModel requestMessage = new MyModel();
requestMessage.setMessage("Hello Request Post");
given(testService1.getMessage(requestMessage)).willReturn(responseMessage);
but the message received to in your controller in your #WebMvcTest is not equal to requestMessage specified in the test. This is due to the fact that MyModel class does not override equals method.
In this situation, Mockito will use its default behaviour:
By default, for all methods that return a value, a mock will return either null, a primitive/primitive wrapper value, or an empty collection, as appropriate. For example 0 for an int/Integer and false for a boolean/Boolean.
You have two options to fix the problem:
override equals (and hashCode) in your request class.
Get acquainted with argument matchers
More info on option 2.:
Technically, your expectation is equivalent to:
given(testService1.getMessage(ArgumentMatchers.eq(requestMessage)))
.willReturn(responseMessage);
You can use other matcher, or even define your own. This is useful if you cannot modify code of your argument's type (type coming from 3-rd party library etc).
For example, you can use ArgumentMatchers.any(MyModel.class))

Related

Testing functions in spring boot which have autowired dependencies

Here is a minimal version of code that I want to test:
#Service
public class Ver {
#Autowired
private DHClient dhclient;
#Autowired
private DHUrlService dhUrlService;
public String getLVersion(String LRversion)
{
String vers=dhclient.get(dhUrlService.getUrl());
return getVersion(vers, LRversion);
}
}
Now the problem with testing this is that. dhclient is autowired inside the Ver class.
The get function makes an HTTP Get request and fetches a response.
I want the test case to actually make the dhclient.get call and return the result.
Any ideas on how to go about testing this?
How do I test that getLVer(String lrVersion) returns null when "test" is passed as the argument?
#Service
public class DHClient {
#Value("${clientId}")
private String clientId;
private HttpClient httpClient;
public String get(String URL){
HttpRequest request// build the HTTP request
HttpResponse<String> response = httpCient.send(request,HttpResponse.BodyHandlers.ofString());
return response.body()
}
}
the above is the code of the DHClient. Since I want to make an actual GET call using the get function. Probably mocking the object is not a good idea. The alternative would be to create an actual object. But this has fields like clientId and httpClient which are initialized by Spring. I am not sure how to create an actual object for this.

Spring MVC Unit Testing - Mocked Service method is not called

I was working on a spring boot project where I have controller which calls a service method and process the output.
I'am using spring MockMvc for testing the web layer. In my test class I have mocked the service method with Mockito.when(). But when I call the corresponding handler method it is not calling the mocked service method instead returns a null response.
Controller
#Controller
public class SocialLoginEndpoints {
#Autowired
#Qualifier("facebookAuth")
SocialLogin faceBookAuth;
#Autowired
#Qualifier("googleAuth")
SocialLogin googleAuth;
#Autowired SignupService signupService;
#GetMapping("/auth/google")
public String googleAuth(#RequestParam String signupType, HttpServletRequest request) {
return "redirect:" + googleAuth.getAuthURL(request, signupType);
}
}
Test Class
#WebMvcTest(SocialLoginEndpoints.class)
class SocialLoginEndpointsTest {
#Autowired MockMvc mockMvc;
MockHttpServletRequest mockHttpServletRequest;
#MockBean GoogleAuth googleAuth;
#MockBean FacebookAuth facebokAuth;
#MockBean SignupService signupService;
#BeforeEach
void setUp() {
mockHttpServletRequest = new MockHttpServletRequest();
}
#Test
void googleAuth() throws Exception {
Mockito.when(googleAuth.getAuthURL(mockHttpServletRequest, "free"))
.thenReturn("www.google.com");
mockMvc
.perform(MockMvcRequestBuilders.get("/auth/google").param("signupType", "free"))
.andExpect(MockMvcResultMatchers.redirectedUrl("www.google.com"))
.andExpect(MockMvcResultMatchers.status().isFound())
.andDo(MockMvcResultHandlers.print());
Mockito.verify(googleAuth, Mockito.times(1)).getAuthURL(mockHttpServletRequest, "free");
}
The reponse which is returned is
MockHttpServletResponse:
Status = 302
Error message = null
Headers = [Content-Language:"en", Location:"null"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
please help me to resolve this issue.
Thanks in Advance !
You stubbed googleAuth.getAuthURL incorrectly.
The MockHttpServletRequest you create in your test and use during stubbing is not the same instance as HttpServletRequest that is sent via MockMvc. Also, they are not equal to each other (Object.equals is used as it is not overriden)
By default Mockito uses equals to verify if arguments in a stubbing match those in the real call. Your stubbing params do not match call params, thus a default value for the method (null) is returned.
The simplest way to fix is to relax argument matchers.
Mockito.when(googleAuth.getAuthURL(
any(HttpServletRequest.class),
eq("free")))
.thenReturn("www.google.com");

how to write Test case for if else using mockMvc in spring boot

How can i write Mockmvc test case for below code:
My controller class
#RestController
public class CartController {
#Autowired
private CartService cartService;
#GetMapping(path = "/addToCart", produces = MediaType.APPLICATION_JSON_VALUE)
public String cart(#Valid #RequestBody Cart cart) {
return cartService.cart(cart);
}
}
My CartService class:
#Service
public class CartService {
private LoginRepository loginRepository;
#Autowired
private ProductRepository productRepository;
#Autowired
private CartRepository cartRepository;
#Autowired
private EmailService emailService;
public CartService(LoginRepository loginRepository) {
this.loginRepository = loginRepository;
}
public String cart(Cart cart) {
String username = cart.getUserName();
System.out.println(username);
String password = cart.getPassword();
String email = cart.getEmail();
if (loginRepository.existsByUserNameAndPassword(username, password)) {
String productname = cart.getProductName();
System.out.println(productname);
String price = cart.getPrice();
String discription = cart.getDiscription();
if (productname != null) {
if (productRepository.existsByProductNameAndPriceAndDiscription(productname, price, discription)) {
Integer count = cartRepository.countByUserName(username);
System.out.println(count);
cartRepository.save(new Cart(username, password, email, productname, price, discription, count));
return "{\"message\":\"product Successfully added to cart\"}";
} else {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "entity not found"
);
}
} else {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
}
} else {
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
}
}
I don't know how to write test case for above code using mockmvc. i don't know how can i write mockmvc test case for if else statement. so please help me how to write mockmvc test case for entire code so that i can do mockmvc test for if else statment also.
I think you have a misunderstanding of what #WebMvcTest is used for.
It is for Integration Testing a single slice of your Application – namely Web MVC.
This means that your #WebMvcTest annotated Test should only use Mocks of your Service because what they should test is all the Web related stuff (proper conversion to JSON, XML; returning correct response codes, etc)
There is a tutorial on spring.io which should answer your questions.
For Testing your Service you can use plain old Unit Test with for example JUnit.
In order for that to work you need to do some rework on your classes. First thing I would suggest is to replace the field injections with constructor injection. This is the recommended way of injection. Read here for more information.
After this change you can mock the dependencies of your Service (for example with Mockito) and pass them via the constructor.
This way you can test the different if/else branches in your code.
And last but not least I would highly recommend to do some other rework on your service. Currently it returns information that are highly coupled to the Web context(the manually crafted JSON, the ResponseStatusException). The handling of those is the responsibility of your controller. Your Service should be independent of the thing (the controller in your case) that uses it. Just imagine your Service will be used by a other Class for a CLI Tool which does know nothing about JSON and response statuses.

Spring boot caching in #Service class does not work

I have problems with save some values in #Service method.
My code:
#Service(value = "SettingsService")
public class SettingsService {
...
public String getGlobalSettingsValue(Settings setting) {
getTotalEhCacheSize();
if(!setting.getGlobal()){
throw new IllegalStateException(setting.name() + " is not global setting");
}
GlobalSettings globalSettings = globalSettingsRepository.findBySetting(setting);
if(globalSettings != null)
return globalSettings.getValue();
else
return getGlobalEnumValue(setting)
}
#Cacheable(value = "noTimeCache", key = "#setting.name()")
public String getGlobalEnumValue(Settings setting) {
return Settings.valueOf(setting.name()).getDefaultValue();
}
My repository class:
#Repository
public interface GlobalSettingsRepository extends CrudRepository<GlobalSettings, Settings> {
#Cacheable(value = "noTimeCache", key = "#setting.name()", unless="#result == null")
GlobalSettings findBySetting(Settings setting);
It should work like this:
get value form DB if data exist,
if not save value from enum.
but it didn't save any data from DB or enum.
My cache config:
#Configuration
#EnableCaching
public class CacheConfig {
#Bean
public EhCacheCacheManager cacheManager(CacheManager cm) {
return new EhCacheCacheManager(cm);
}
#Bean
public EhCacheManagerFactoryBean ehcache() {
EhCacheManagerFactoryBean ehCacheManagerFactoryBean = new EhCacheManagerFactoryBean();
ehCacheManagerFactoryBean.setConfigLocation(new ClassPathResource("ehcache.xml"));
return ehCacheManagerFactoryBean;
}
}
I have some example to make sure that cache is working in my project in rest method:
#RequestMapping(value = "/system/status", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> systemStatus() {
Object[] list = userPuzzleRepository.getAverageResponseByDateBetween(startDate, endDate);
...
}
public interface UserPuzzleRepository extends CrudRepository<UserPuzzle, Long> {
#Cacheable(value = "averageTimeAnswer", key = "#startDate")
#Query("select AVG(case when up.status='SUCCESS' OR up.status='FAILURE' OR up.status='TO_CHECK' then up.solvedTime else null end) from UserPuzzle up where up.solvedDate BETWEEN ?1 AND ?2")
Object[] getAverageResponseByDateBetween(Timestamp startDate, Timestamp endDate);
and it's work well.
What am I doing wwrong?
You have two methods in your SettingsService, one that is cached (getGlobalEnumValue(...)) and another one that isn't cached, but calls the other method (getGlobalSettingsValue(...)).
The way the Spring cache abstraction works however is by proxying your class (using Spring AOP). However, calls to methods within the same class will not call the proxied logic, but the direct business logic beneath. This means caching does not work if you're calling methods in the same bean.
So, if you're calling getGlobalSettingsValue(), it will not populate, nor use the cache when that method calls getGlobalEnumValue(...).
The possible solutions are:
Not calling another method in the same class when using proxies
Caching the other method as well
Using AspectJ rather than Spring AOP, which weaves the code directly into the byte code at compile time, rather than proxying the class. You can switch the mode by setting the #EnableCaching(mode = AdviceMode.ASPECTJ). However, you'll have to set up load time weaving as well.
Autowire the service into your service, and use that service rather than calling the method directly. By autowiring the service, you inject the proxy into your service.
The problem is in the place you call your cacheable method from. When you call your #Cacheable method from same class, you just call it from this reference, which means it doesn't wrapped by Spring's proxy, so Spring can't catch your invocation to handle it.
One on ways to solve this problem is to #Autowired service to itself and just call methods you expected spring have to handle by this reference:
#Service(value = "SettingsService")
public class SettingsService {
//...
#Autowired
private SettingsService settingsService;
//...
public String getGlobalSettingsValue(Settings setting) {
// ...
return settingsSerive.getGlobalEnumValue(setting)
//-----------------------^Look Here
}
#Cacheable(value = "noTimeCache", key = "#setting.name()")
public String getGlobalEnumValue(Settings setting) {
return Settings.valueOf(setting.name()).getDefaultValue();
}
}
But if you have such problems it means your classes are take on too much and aren't comply with the principle of "single class - single responsibility". The better solution would be to move method with #Cacheable to dedicated class.

Object reference changing during Mockito Test

I have just started working with Mockito and am having a problem with one of the tests failing, yet the actual code works correctly in a live environment. The controller being tested is:
#Controller
#RequestMapping("/notes")
public class NotesController {
private NoteRepository noteRepository;
private MyUserRepository userRepository;
#RequestMapping(value = "/add", method = POST)
public String postNote(#Valid Note note, BindingResult errors, Principal principal){
String username = principal.getName();
MyUser user = userRepository.findUserByUsername(username);
note.setMyUser(user);
note.setTime(new Date());
noteRepository.save(note);
return "redirect:/notes"; }
}
The test is here:
#Test
public void testShouldAddValidNote() throws Exception {
MyUser testing = new MyUser();
Note note = new Note();
NoteRepository noteRepository = mock(NoteRepository.class);
when(noteRepository.save(note)).thenReturn(note);
MyUserRepository userRepository = mock(MyUserRepository.class);
when(userRepository.findUserByUsername("testing")).thenReturn(testing);
Principal mockPrincipal = mock(Principal.class);
when(mockPrincipal.getName()).thenReturn("testing");
NoteController controller = new NoteController(noteRepository);
controller.setMyUserRepository(userRepository);
MockMvc mockMvc = standaloneSetup(controller).build();
mockMvc.perform(post("/notes/add")
.requestAttr("note", note)
.principal(mockPrincipal))
.andExpect(view().name("redirect:/notes"));
verify(noteRepository,times(1)).save(note);
verify(note,times(1)).setMyUser(testing);
verify(note,times(1)).setTime((Date)anyObject());
verify(userRepository,times(1)).findUserByUsername("testing");
verify(mockPrincipal,times(1)).getName();
}
The first verify test fails, with the message:
Argument(s) are different! Wanted:
noteRepository.save(projectName.Note#5ae9);
Actual invocation has different arguments:
noteRepository.save(projectName.Note#c079ae45
Clearly the Note object passed in to the method has changed, but I thought that using .requestAttr("note", note) would pass in the reference and the same object should therefore be in the method (and later returned). Like I said, it works perfectly in the live web Container, so what am I getting wrong with the Mockito test please?
This is just a wild guess could the issue this code faces comes from MockMvc / MockMvcRequestBuilders where the Note is somehow serialized / deserialized between the request configuration and the actual request ?
Note that note is a real object so calling verify(note).... won't work.
Anyway I suggest the use of the combination of a mockito captor and AssertJ in this case :
// if field instantiation if using the mockito runner / rule or MockitoAnnotations.initMocks(this)
#Captor ArgumentCaptor<Note> noteCaptor;
// if created in the test method
ArgumentCaptor<Note> noteCaptor = ArgumentCaptor.forClass(Note.class);
// ...
verify(noteRepository,times(1)).save(noteCaptor.capture());
assertThat(noteCaptor.getValue().geMyUser()).isEqualTo(testing);
assertThat(noteCaptor.getValue().geTime()).isCloseTo(someDate);
Note I'm on a phone

Resources