I end up with my repository as null when testing a method in a service class. It is unclear what causes this for me.
In my service class I make use of a private RestTemplate that I setup as follows together with repository as #Mocks and service class #Injectmocks on :
...
#InjectMocks
#Spy
WorkService workService;
#Mock
private WorkJobRepository workJobRepository;
...
#Mock
RestTemplateBuilder mockedBuilder= Mockito.mock(RestTemplateBuilder.class);
#Mock
RestTemplate mockedRestTemplate = Mockito.mock(RestTemplate.class);
#BeforeEach
void setUp() {
ReflectionTestUtils.setField(listToPrintService, "restTemplate", mockedRestTemplate);
}
My test case in which repository is not passed (it is null):
#Test
void testGetRequestListToWork() {
ListToWork listToWork = getListToWork();
WorkJob WorkJob = getOneWorkJob();
ResponseEntity<String> responseEntity = getOneResponseEntityString(listToWork);
Mockito.doReturn(responseEntity).when(listToWorkService).getRequestListToWork(listToWork);
Mockito.when(listToWorkService.getRequestListToWork(listToWork)).thenAnswer(new Answer<ResponseEntity<String>>() {
#Override
public ResponseEntity<String> answer(InvocationOnMock invocation) {
return responseEntity;
}
});
String json = responseEntity.getBody().toString();
Mockito.when(listToWorkService.createNewWorkJob(json, listToWork)).thenReturn(WorkJob);
listToWorkService.getRequestListToWork(WorkJob);
assertEquals("xxx", repository.getById(listToWork.getId));
}
Here's the method I am trying to test:
public WorkJob getRequestListToWork(WorkJob WorkJob) {
ListToWork listToWork = new ListToWork(WorkJob.getTemplateId(), WorkJob.getStoreName(), WorkJob.getJobListId(), WorkJob.getId());
ResponseEntity<String> responseEntity = getRequestListToWork(listToWork);
Gson g = new Gson();
String json = responseEntity.getBody().toString();
WorkJob newWorkJob = createNewWorkJob(json,listToWork);
if(newWorkJob.getJobId() != -1 && newWorkJob.getJobState() != 12) {
pollForJobStatus(newWorkJob);
}
return newWorkJob;
}
Can anybody advise? What is causing the repository not to be passed?
Related
I am trying to create the simple web api test for the controller action method I have in my project. I already create and add the test project in my solution. And add the Nunit nuget package in test project.
The controller I am trying to test is look like this:
[ApiController]
[Route("[controller]")]
public class HomeController : ControllerBase
{
private readonly IConfiguration _configuration;
private readonly IHostEnvironment _hostEnvironment;
private readonly ILogger<HomeController> _logger;
private BaseDataAccess _datatAccess = new BaseDataAccess()
public HomeController(ILogger<HomeController> logger, IConfiguration configuration, IHostEnvironment hostEnvironment)
{
_logger = logger;
_configuration = configuration;
_hostEnvironment = hostEnvironment;
}
[HttpGet("GetInfo/{code}")]
public IActionResult GetInfo(string code)
{
List<InfoModel> infos = new List<InfoModel>();
int isNumber;
if (String.IsNullOrEmpty(code) || !int.TryParse(code, out isNumber))
{
_logger.LogInformation(String.Format("The code pass as arguments to api is : {0}", code));
return BadRequest("Invalid code");
}
try
{
_logger.LogDebug(1, "The code passed is" + code);
SqlConnection connection = _datatAccess.GetConnection(_configuration, _hostEnvironment);
string sql = string.Format ("SELECT * from table1 where code={0}", code);
DataTable dt = _datatAccess.ExecuteQuery(connection,CommandType.Text, sql);
if (dt != null && dt.Rows.Count > 0)
{
foreach (DataRow dr in dt.Rows)
{
infos.Add(new InfoModel
{
ID = dr["id"].ToString(),
code = dr["code"].ToString()
});
}
}
}
catch (Exception ex)
{
_logger.LogError(4, String.Format("Error Message: " + ex.Message + "\n" + ex.StackTrace));
return BadRequest("There is something wrong.Please contact the administration.");
}
return new OkObjectResult(infos);
}
}
Now when I try to create the unit test I need to pass the configuration, hostenvironment and logger to HomeController from my TestHomeController. And I don't know how to instantiate these settings and pass to controller:
using NUnit.Framework;
using Microsoft.AspNetCore.Mvc;
using MyApi.Models;
using MyApi.Controllers;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace MyApi.Tests
{
[TestFixture]
public class TestHomeController: ControllerBase
{
private readonly IConfiguration _configuration; //How to instantiate this so it is not null
private readonly IHostEnvironment _hostEnvironment ;//How to instantiate this so it is not null
private ILogger<HomeController> _logger;//How to instantiate this so it is not null
[Test]
public void GetInfo_ShouldReturnAllInfo()
{
var controller = new HomeConteoller(_logger, _configuration, _hostEnvironment);
var result = controller.GetInfo("11");
var okObjectResult = (OkObjectResult)result;
//Assert
okObjectResult.StatusCode.Equals(200);
}
}
}
Thanks for any help and suggestions.
Probably, you have startup.cs. Don't you?
if you gonna test a controller, then you need to build a whole instance of an application. Here I put an example of how you can test your code if you have Startup.cs.
public class SUTFactory : WebApplicationFactory<Startup>
{
protected override IHostBuilder CreateHostBuilder()
{
return Program.CreateHostBuilder(null);
}
}
public class TestControllerTests
{
private SUTFactory factory;
private HttpClient _client;
public TestControllerTests()
{
factory = new SUTFactory();
_client = factory.CreateClient();
}
[Test]
public async Task GetPatientInterviewID_ShouldReturnAllInterviewID()
{
// Arrange
var id = "11";
// Act
var result = await _client.GetAsync($"Home/GetInfo/{id}");
// Assert
Assert.AreEqual(System.Net.HttpStatusCode.OK, result.StatusCode);
}
}
This example is closer to Integration testing rather than Unit-testing. If you want to have unit-test then you need to do the following things
BaseDataAccess _datatAccess this is a specific realization and it cannot be mocked (comparing to ILogger, IHostEnvironment etc)
move all your code from the controller to a separate class, and test this class.
I am trying to write Unit test to the handler function, I followed the example from the Spring project. Can someone help me why the following test is throwing UnsupportedMediaTypeStatusException?
Thanks
Handler function
public Mono<ServerResponse> handle(ServerRequest serverRequest) {
log.info("{} Processing create request", serverRequest.exchange().getLogPrefix());
return ok().body(serverRequest.bodyToMono(Person.class).map(p -> p.toBuilder().id(UUID.randomUUID().toString()).build()), Person.class);
}
Test Class
#SpringBootTest
#RunWith(SpringRunner.class)
public class MyHandlerTest {
#Autowired
private MyHandler myHandler;
private ServerResponse.Context context;
#Before
public void createContext() {
HandlerStrategies strategies = HandlerStrategies.withDefaults();
context = new ServerResponse.Context() {
#Override
public List<HttpMessageWriter<?>> messageWriters() {
return strategies.messageWriters();
}
#Override
public List<ViewResolver> viewResolvers() {
return strategies.viewResolvers();
}
};
}
#Test
public void handle() {
Gson gson = new Gson();
MockServerWebExchange exchange = MockServerWebExchange.from(
MockServerHttpRequest.post("/api/create")
.body(gson.toJson(Person.builder().firstName("Jon").lastName("Doe").build())));
MockServerHttpResponse mockResponse = exchange.getResponse();
ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
Mono<ServerResponse> serverResponseMono = myHandler.handle(serverRequest);
Mono<Void> voidMono = serverResponseMono.flatMap(response -> {
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
boolean condition = response instanceof EntityResponse;
assertThat(condition).isTrue();
return response.writeTo(exchange, context);
});
StepVerifier.create(voidMono)
.expectComplete().verify();
StepVerifier.create(mockResponse.getBody())
.consumeNextWith(a -> System.out.println(a))
.expectComplete().verify();
assertThat(mockResponse.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
}
}
Error Message:
java.lang.AssertionError: expectation "expectComplete" failed (expected: onComplete(); actual: onError(org.springframework.web.server.UnsupportedMediaTypeStatusException: 415 UNSUPPORTED_MEDIA_TYPE "Content type 'application/octet-stream' not supported for bodyType=com.example.demo.Person"))
I found that I missed .contentType(MediaType.APPLICATION_JSON) to my mock request.
MockServerWebExchange.from(
MockServerHttpRequest.post("/api/create").contentType(MediaType.APPLICATION_JSON)
.body(gson.toJson(Person.builder().firstName("Jon").lastName("Doe").build())));
fixed my issue.
The controller method I am testing
#GetMapping("/customers")
#ResponseBody
public DeferredResult<ResponseEntity<Resources<Resource<Customer>>>> getAllCustomers(
#PageableDefault(page = 0, size = 20) #SortDefault.SortDefaults({
#SortDefault(sort = "name", direction = Direction.ASC) }) Pageable pageable,
PagedResourcesAssembler<Customer> assembler, HttpServletRequest request) {
DeferredResult<ResponseEntity<Resources<Resource<Customer>>>> response = new DeferredResult<>(
Long.valueOf(1000000));
response.onTimeout(() -> response
.setErrorResult(ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT).body("Request timed out.")));
response.onError((Throwable t) -> {
response.setErrorResult(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("An error occured."));
});
ListenableFuture<Page<Customer>> future = customerService.findAll(pageable);
future.addCallback(new ListenableFutureCallback<Page<Customer>>() {
#Override
public void onSuccess(Page<Customer> result) {
Link self = new Link(
ServletUriComponentsBuilder.fromRequestUri(request).buildAndExpand().toUri().toString(),
"self");
LOGGER.debug("Generated Self Link {} for Customer Resource Collection", self.getHref());
if (result.hasContent())
response.setResult(
ResponseEntity.ok(assembler.toResource(result, customerResourceAssembler, self)));
else
response.setErrorResult(ResponseEntity.notFound());
LOGGER.debug("Returning Response with {} customers", result.getNumber());
}
#Override
public void onFailure(Throwable ex) {
LOGGER.error("Could not retrieve customers due to error", ex);
response.setErrorResult(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Could not save customers list due to server error."));
}
});
return response;
}
the unit test
#RunWith(SpringRunner.class)
#WebMvcTest(CustomerController.class)
#EnableSpringDataWebSupport
#Import({ CustomerResourceAssember.class, BranchResourceAssembler.class, InvoiceResourceAssembler.class,
CustomerAsyncService.class })
public class CustomerControllerTests {
#Autowired
private MockMvc mockMvc;
#Autowired
CustomerAsyncService customerService;
#MockBean
private CustomerRepository customerRepository;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void testWhenNoCustomersThenReturnsEmptyHALDocument() throws Exception {
// Given
BDDMockito.given(customerRepository.findAll(PageRequest.of(0, 20)))
.willReturn(new PageImpl<Customer>(Collections.emptyList()));
// When
MvcResult result = mockMvc.perform(get("/customers").accept(MediaTypes.HAL_JSON_VALUE)).andDo(print())
.andExpect(request().asyncStarted())
.andExpect(request().asyncResult(new PageImpl<Customer>(Collections.emptyList()))).andReturn();
// Then
mockMvc.perform(asyncDispatch(result)).andExpect(status().isOk());
}
This test neve completes, doesn't even time out on my IDE, I have to kill it everytime I run it, if run the entire app however this /customers endpoint gives a 404 when there are no customers added to the application.
What do I need to do make sure this test completes, the CustomerService call ultimately calls CustomerRepository which I have mocked because I couldn't get my brains around how to mock the async call to service method. the customer service class is as follows
#Async
#Service
public class CustomerAsyncService {
private CustomerRepository customerRepository;
#Autowired
public CustomerAsyncService(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
#Transactional(readOnly = true, isolation = Isolation.SERIALIZABLE)
public ListenableFuture<Page<Customer>> findAll(Pageable pageable) {
return AsyncResult.forValue(customerRepository.findAll(pageable));
}
I was hoping mocking the Repository method would do the trick. How do I mock the async service call
My bad was using mocks wrongly, this worked
#RunWith(SpringRunner.class)
#WebMvcTest(CustomerController.class)
#Import({ CustomerResourceAssember.class, BranchResourceAssembler.class, InvoiceResourceAssembler.class,
CustomerAsyncService.class })
public class CustomerControllerTests {
#MockBean
private CustomerRepository customerRepository;
#InjectMocks
CustomerAsyncService customerService = new CustomerAsyncService(customerRepository);
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
JacksonTester.initFields(this, objectMapper);
}
#Test
public void testReturnsNotFoundForEmptyGetAllCustomersResult() throws Exception {
// Given
Page<Customer> emptyPage = new PageImpl<Customer>(Collections.emptyList());
BDDMockito.given(customerRepository.findAll(any(Pageable.class))).willReturn(emptyPage);
// When
MvcResult result = mockMvc.perform(get("/customers")).andExpect(request().asyncStarted()).andDo(print()).andReturn();
// Then
mockMvc.perform(asyncDispatch(result)).andDo(print()).andExpect(status().isNotFound());
}
}
I have the following code I'm trying to unit test :
if (networkUtils.isOnline()) {
return remoteDataSource.postComment(postId, commentText)
.doOnSuccess(postCommentResponse ->
localDataSource.postComment(postId, commentText))
.subscribeOn(schedulerProvider.io())
.observeOn(schedulerProvider.mainThread());
} else {
return Single.error(new IOException());
}
And this is how I'm trying to test it :
#Test
public void postComment_whenIsOnline_shouldCallLocalToPostComment() throws Exception {
// Given
when(networkUtils.isOnline())
.thenReturn(true);
String postId = "100";
String comment = "comment";
Response<PostCommentResponse> response = postCommentResponse();
when(remoteDataSource.postComment(anyString(), anyString()))
.thenReturn(Single.just(response));
// When
repository.postComment(postId, comment);
// Then
verify(localDataSource).postComment(postId, comment);
}
where I fake Response from Retrofit like :
private Response<PostCommentResponse> postCommentResponse() {
PostCommentResponse response = new PostCommentResponse();
response.setError("0");
response.setComment(postCommentResponseNestedItem);
return Response.success(response);
}
but it results to : Actually, there were zero interactions with this mock.
Any ideas ?
EDIT :
#RunWith(MockitoJUnitRunner.class)
public class CommentsRepositoryTest {
#Mock
private CommentsLocalDataSource localDataSource;
#Mock
private CommentsRemoteDataSource remoteDataSource;
#Mock
private NetworkUtils networkUtils;
#Mock
private PostCommentResponseNestedItem postCommentResponseNestedItem;
private CommentsRepository repository;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
BaseSchedulerProvider schedulerProvider = new ImmediateSchedulerProvider();
repository = new CommentsRepository(localDataSource, remoteDataSource, networkUtils, schedulerProvider);
}
// tests
}
When you want to test an Observable you have to subscribe to it so it will start emitting items.
As soon as I used :
TestObserver<Response<PostCommentResponse>> testObserver = new TestObserver<>();
and subscribed to :
repository.postComment(postId, comment)
.subscribe(testObserver);
the test worked as expected.
I am trying to test the following Spring mvc controller method:
#RequestMapping(value = "/preferences/email", method = RequestMethod.POST, produces = "text/html")
public String modifyEmail(#ModelAttribute #Validated({ Validation.EmailModification.class }) EmailInfo emailInfo, BindingResult bindingResult, Model model, Locale locale) {
Member member = memberService.retrieveCurrentMember();
if (!preferencesService.isEmailAvailable(emailInfo.getEmail())) {
if (member.getEmail().equals(emailInfo.getEmail())) {
bindingResult.addError(new FieldError("emailInfo", "email", messageSource.getMessage("controller.preferences.same_email", null, locale)));
} else {
bindingResult.addError(new FieldError("emailInfo", "email", messageSource.getMessage("controller.preferences.email_already_used", null, locale)));
}
}
if (bindingResult.hasErrors()) {
model.addAttribute("emailInfo", emailInfo);
return "preferences";
}
preferencesService.modifyEmail(member, emailInfo.getEmail());
return "redirect:/preferences/email";
}
Here is the EmailInfo bean:
#RooEquals
#RooJavaBean
public class EmailInfo {
#NotNull(groups = { Validation.EmailModification.class })
#Pattern(regexp = "^[_a-z0-9-]+(\\.[_a-z0-9-]+)*#[a-z0-9-]+(\\.[a-z0-9-]+)+$", groups = { Validation.EmailModification.class })
private String email;
private boolean activated;
private String token;
}
Here is the test class:
#ContextConfiguration
#WebAppConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
public class PreferenceControllerTest {
#Autowired
private WebApplicationContext ctx;
private MockMvc mockMvc;
#Autowired
private MemberService memberService;
#Autowired
private PreferencesService preferencesService;
#Autowired
private MemberRepository memberRepository;
#Autowired
private SigninService signinService;
#Autowired
private MessageSource messageSource;
#Before
public void setup() {
mockMvc = webAppContextSetup(ctx).build();
Member currentMember = new Member();
currentMember.setEmail("currentMember#example.com");
when(memberService.retrieveCurrentMember()).thenReturn(currentMember);
when(preferencesService.isEmailAvailable("notAvailable#example.com")).thenReturn(Boolean.FALSE);
}
#Test
public void test() throws Exception {
mockMvc.perform(post("/preferences/email")//
.param("email", "newEmail#example.com"))//
.andDo(print()).andExpect(model().attributeHasNoErrors("emailInfo", "email"));
}
#Configuration
public static class testConfiguration {
#Bean
public PreferenceController preferenceController() {
return new PreferenceController();
}
#Bean
public PreferencesService preferenceService() {
return mock(PreferencesService.class);
}
#Bean
public MemberService memberService() {
return mock(MemberService.class);
}
#Bean
public MemberRepository memberRepository() {
return mock(MemberRepository.class);
}
#Bean
public SigninService signinService() {
return mock(SigninService.class);
}
#Bean
public MessageSource messageSource() {
return mock(MessageSource.class);
}
}
}
Curiously I get the following output:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /preferences/email
Parameters = {email=[newEmail#example.com]}
Headers = {}
Handler:
Type = com.bignibou.controller.PreferenceController
Async:
Was async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = preferences
View = null
Attribute = emailInfo
value = com.bignibou.controller.helpers.EmailInfo#9a56c123
errors = [Field error in object 'emailInfo' on field 'email': rejected value [null]; codes []; arguments []; default message [null]]
FlashMap:
MockHttpServletResponse:
Status = 200
Error message = null
Headers = {}
Content type = null
Body =
Forwarded URL = preferences
Redirected URL = null
Cookies = []
The test fails with the above output and I am not sure why. I expected the test to pass as the email address is available.
Can anyone please help?
edit 1:
The following is not working either:
#Before
public void setup() {
mockMvc = webAppContextSetup(ctx).build();
Member currentMember = new Member();
currentMember.setEmail("currentMember#example.com");
when(memberService.retrieveCurrentMember()).thenReturn(currentMember);
when(preferencesService.isEmailAvailable(eq("notAvailable#example.com"))).thenReturn(Boolean.FALSE);
when(preferencesService.isEmailAvailable(eq("newEmail#example.com"))).thenReturn(Boolean.TRUE);
}
edit 2:
I was able to get is to work with the above edit 1 plus the test below:
#Test
public void test() throws Exception {
mockMvc.perform(post("/preferences/email")//
.param("email", "available#example.com"))//
.andDo(print())//
.andExpect(model().attributeHasNoErrors("emailInfo"));
}
With this :
.param("email", "newEmail#example.com"))//
You are setting request parameter to the string value. However you have not shown your conversion from String to EmailInfo.
In your test you are checking the field of emailInfo called email.
I am not sure what this is for ?
when(preferencesService.isEmailAvailable("notAvailable#example.com")).thenReturn(Boolean.FALSE);
What is supposed to do, you have injected your preferenceService using autowired.
Updae to answer comment.
in your controller try
String email=emailInfo.getEmail();
if(!preferencesService.isEmailAvailable(email))){ instead of if (!preferencesService.isEmailAvailable(emailInfo.getEmail())) {
Not sure, just a possible solution
Or try
when(preferencesService.isEmailAvailable(eq("newEmail#example.com"))).thenReturn(Boolean.TRUE);
when(preferencesService.isEmailAvailable(eq("notAvailable#example.com"))).thenReturn(Boolean.FALSE);
Ae you using Mockito to implement mocking?
I am not 100% sure but here is How I understand your code.
when(preferencesService.isEmailAvailable("notAvailable#example.com")).thenReturn(Boolean.FALSE);
if preferencesService.isEmailAvailable returns true then you are forcefully returning false in mock exercise
so when in mock exercise preferencesService.isEmailAvailable will always return false.
Now in your Controller
if (!preferencesService.isEmailAvailable(emailInfo.getEmail())) {
if (member.getEmail().equals(emailInfo.getEmail())) {
bindingResult.addError(new FieldError("emailInfo", "email", messageSource.getMessage("controller.preferences.same_email", null, locale)));
} else {
bindingResult.addError(new FieldError("emailInfo", "email", messageSource.getMessage("controller.preferences.email_already_used", null, locale)));
}
}
If preferencesService.isEmailAvailable is false then ! make it true so code will always go inside if Block , and you will get Field Error, and hence Test fails.