#ModelAttribute in Spring Boot application - java

I have a Spring Boot application (embedded Tomcat, Thymeleaf template...)
Here one of my controllers:
#Controller
#RequestMapping("/company")
public class CompanyController {
#RequestMapping(value = { "/list" }, method = { RequestMethod.GET })
public String companyList(Company company, ModelMap model) {
return serverContextPath + COMPANY_LIST_VIEW_NAME;
}
#ModelAttribute("companies")
public Iterable<Company> companies(){
return companyService.findAll();
}
}
I've created this Junit Test to verify that my controller works fine
#ContextConfiguration(classes = { MyApplicationConfig.class })
public class CompanyControllerTests {
private CompanyController controller;
#Value("${server.contextPath}")
protected String serverContextPath;
#Before
public void setUp() throws Exception {
controller = new CompanyController();
}
#Test
public void testCompanyList() {
ExtendedModelMap model = new ExtendedModelMap();
String viewName = controller.companyList(new Company(), model);
assertEquals(serverContextPath + CompanyController.COMPANY_LIST_VIEW_NAME, viewName);
Iterable<Company> companies = (Iterable<Company>) model.get("companies");
assertNotNull(companies);
}
}
But I have an AssertionError assertNotNull(companies);

Probably you need to mock this call with some expected result:
companyService.findAll()
to be able to test controller apart from services

Related

Factory design patter Spring Boot double bean

#Component
public abstract class CommandBase {
#Autowired
WebServiceProxy nbiService;
#Autowired
OperationCacheRepository cacheRepository;
public CommandBase(
WebServiceProxy nbiService,
OperationCacheRepository cacheRepository) {
this.nbiService = nbiService;
this.cacheRepository = cacheRepository;
}
public abstract void executeSPV(SpeedTestDTO stDTO) throws NBIException;
public abstract long executeGPV(long guid, OperationCache operationCache) throws NBIException;
#Slf4j
public class DownloadDiagnosticsCommand extends CommandBase {
public DownloadDiagnosticsCommand(WebServiceProxy nbiService, OperationCacheRepository cacheRepository) {
super(nbiService, cacheRepository);
}
#Override
public void executeSPV(SpeedTestDTO stDTO) throws NBIException {
// some executable code
}
#Override
public long executeGPV(long guid, OperationCache operationCache) throws NBIException {
// some executable code
}
}
#Slf4j
public class UploadDiagnosticsCommand extends CommandBase {
public UploadDiagnosticsCommand(WebServiceProxy nbiService, OperationCacheRepository cacheRepository) {
super(nbiService, cacheRepository);
}
#Override
public void executeSPV(SpeedTestDTO stDTO) throws NBIException {
// some executable code
}
#Override
public long executeGPV(long guid, OperationCache operationCache) throws NBIException {
//some executable code
}
}
#Component
public class RFACommandFactory {
#Autowired
WebServiceProxy nbiServiceProxy;
#Autowired
OperationCacheRepository cacheRepository;
public final CommandBase createCommand(final String measureType) {
if ("download".equalsIgnoreCase(measureType)) {
return new DownloadDiagnosticsCommand(nbiServiceProxy, cacheRepository);
} else if ("upload".equalsIgnoreCase(measureType)) {
return new UploadDiagnosticsCommand(nbiServiceProxy, cacheRepository);
}
return null;
}
}
Calling method executeSPV from abstract class
#RestController
#RequestMapping("/rfa/speedtest/v1")
#Slf4j
public class Controller {
#Autowired
CommandBase command;
#Autowired
RFACommandFactory rfaCommandFactory;
#PostMapping(value = "{id}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
private ResponseEntity<String> post(
#PathVariable String assetId,
#RequestBody Payload payload) {
log.info("Received new payload:{}", payload);
command = rfaCommandFactory.createCommand(speedTestDTO.getType());
try {
command.executeSPV(speedTestDTO);
} catch (NBIException e) {
log.info("NBIException", e);
return new ResponseEntity(payload, HttpStatus.BAD_REQUEST);
}
return new ResponseEntity(payload, HttpStatus.CREATED);
}
}
If I remove #Componet from Upload and Download classes I receive error I need to add Bean for abstrcat class CommndBase
If I use #Compoment on Upload and Download classes I receive dual Bean is useed...
Field command in .Controller required a single bean, but 2 were found:
You should not use #Component for abstract class, because Spring context will not be able to initialize that bean. You should remove it then.
Another thing is the way you want to implement a factory pattern here - I recommend you the way described here: https://stackoverflow.com/a/39361500/14056755, refactored version https://stackoverflow.com/a/55060326/14056755.

Spring Unit Test Rest Controller By Setting Private Fields

I have a simple Rest Controller as below
#RestController
public class HealthController {
private static final CustomLogger logger = CustomLogger.getLogger(HealthController.class.getName());
private HealthService healthService;
#Autowired
public HealthController(HealthService healthService) {
this.healthService = healthService;
}
#RequestMapping(value = "/health", method = RequestMethod.GET)
public ResponseEntity<?> healthCheck() {
return healthService.checkHealth();
}
}
The service class is below
#Service
public class HealthService {
private static final CustomLogger logger = CustomLogger.getLogger(HealthController.class.getName());
public ResponseEntity<?> checkHealth() {
logger.info("Inside Health");
if (validateHealth()) {
return new ResponseEntity<>("Healthy", HttpStatus.OK);
} else {
return new ResponseEntity<>("Un Healthy", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
boolean validateHealth() {
return true;
}
}
The corresponding unit test for the controller class as below
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = HealthController.class)
public class HealthControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private HealthService healthService;
#Test
public void checkHealthReturn200WhenHealthy() throws Exception {
ResponseEntity mockSuccessResponse = new ResponseEntity("Healthy", HttpStatus.OK);
when(healthService.checkHealth()).thenReturn(mockSuccessResponse);
RequestBuilder requestBuilder = MockMvcRequestBuilders.get(
"/health").accept(
MediaType.APPLICATION_JSON);
MvcResult healthCheckResult = mockMvc
.perform(requestBuilder).andReturn();
Assert.assertEquals(HttpStatus.OK.value(), healthCheckResult.getResponse().getStatus());
}
}
The problem I have is my CustomLogger. Since it has external dependencies am having issues in trying to test this.The same kind of logger is present in my service classes too.
How can I test such a class. I tried the below stuffs
Created a custom class name CustomLoggerForTest under test. Used
ReflectionTestUtils.setField(healthService, "logger", new CustomerLoggerForTest(HealthService.class.getName()));
in the setUp. But it did not help. Using this we cannot set the static fields hence tried even converting them to be non-static
Tried with mocking the CustomLogger in setup as below
mockStatic(CustomLogger.class); when(CustomLogger.getLogger(any())) .thenReturn(new CustomLoggerForTest(HealthController.class.getName()));
But no luck.
Is there anything that am doing wrong that is causing this?

Spring Boot - Mock a POST REST request to an external API

I have a Spring-Boot 1.5.21 application that serves as a REST gateway between an Angular UI and an external API that provides the data (long story - acts as auth between UI and datasource). A request comes to the Spring-Boot application, it calls the data source API with the request payload.
I am new to Unit Testing for Spring-Boot and am trying to write a test for the POST REST method in the Gateway application that creates a new record (create). I've read a couple of tutorials and other websites detailing how to unit test Spring-Boot APIs but nothing that helps me in my situation.
I want to:
Unit test the REST Controller method and check that the #RequestBody is valid
I do not want a record created in the datasource
Controller Method:
#PostMapping(value = "/" + Constants.API_CHANGE_REQUEST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public String submitChangeRequest(#RequestBody ChangeRequestWrapper changeRequestWrapper) {
logger.info("API Request: Posting Change Request: " + changeRequestWrapper.toString());
return restService.makeApiPost(sharedDataService.buildApiUrlPath(Constants.API_CHANGE_REQUEST), changeRequestWrapper);
}
AppConfig:
#PropertySource({"classpath:application.properties"})
#Configuration
public class AppConfig {
#Resource
private Environment env;
#Bean
public RestTemplate restTemplate() {
RestTemplateBuilder builder = new RestTemplateBuilder();
return builder
.setConnectTimeout(Constants.API_TIMEOUT_CONNECT)
.setReadTimeout(Constants.API_TIMEOUT_READ)
.basicAuthorization(env.getProperty("bpm.user"), env.getProperty("bpm.password"))
.build();
}
}
RestServiceImpl:
#Service
public class RestServiceImpl implements RestService {
private static final Logger logger = LoggerFactory.getLogger(RestServiceImpl.class);
#Autowired
private RestTemplate myRestTemplate;
#Value("${bpm.url}")
private String restUrl;
public String getApiUri() {
return restUrl;
}
public String makeApiCall(String payload) /*throws GradeAdminException */{
logger.info("Implementing API call.");
logger.debug("userApi: " + payload);
return myRestTemplate.getForObject(payload, String.class);
}
public String makeApiPost(String endpoint, Object object) {
logger.info("Implementing API post submission");
logger.debug("userApi endpoint: " + endpoint);
return myRestTemplate.postForObject(endpoint, object, String.class);
}
}
SharedDataServiceImpl:
#Service
public class SharedDataServiceImpl implements SharedDataService {
#Autowired
private RestService restService;
#Override
public String buildApiUrlPath(String request) {
return buildApiUrlPath(request, null);
}
#Override
public String buildApiUrlPath(String request, Object parameter) {
String path;
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(restService.getApiUri());
if (parameter != null) {
builder = builder.path(getApiPath(request) + "/{object}");
UriComponents buildPath = builder.buildAndExpand(parameter);
path = buildPath.toUriString();
} else {
builder = builder.path(getApiPath(request));
path = builder.build().toUriString();
}
return path;
}
}
What I've done for the GET methods:
#RunWith(SpringRunner.class)
#WebMvcTest(ClientDataRequestController.class)
#ContextConfiguration(classes = { TestConfig.class }, loader = AnnotationConfigWebContextLoader.class)
public class ClientDataRequestControllerTest {
#Autowired
private MockMvc mvc;
#Before
public void setUp() {
}
#Test
public void test_no_endpoint() throws Exception {
this.mvc.perform(get("/")).andExpect(status().isNotFound()).andReturn();
}
#Test
public void test_controller_no_endpoint() throws Exception {
this.mvc.perform(get("/api/")).andExpect(status().isOk()).andReturn();
}
#Test
public void test_getStudent_valid_parameters() throws Exception {
this.mvc.perform(get("/api/students/?pidm=272746")).andExpect(status().isOk()).andReturn();
}
}
I would greatly appreciate some assistance with this.
Solution:
I've since found this SO answer that has solved my problem.
You could mock the RestServiceImpl. Add a dependency in your test and annotate it with MockBean:
#MockBean
private RemoteService remoteService;
Now you can go ahead and mock the methods:
import org.mockito.BDDMockito;
BDDMockito.given(this.remoteService.makeApiPost()).willReturn("whatever is needed for your test");

How to set the context path in Spring Boot WebMvcTest

I'm trying to test my Rest controllers from my Spring Boot application and want the controllers to be available under the same path as in production.
For example I have the following Controller:
#RestController
#Transactional
public class MyController {
private final MyRepository repository;
#Autowired
public MyController(MyRepository repository) {
this.repository = repository;
}
#RequestMapping(value = "/myentity/{id}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public ResponseEntity<Resource<MyEntity>> getMyEntity(
#PathVariable(value = "id") Long id) {
MyEntity entity = repository.findOne(id);
if (entity == null) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
return new ResponseEntity<>(entity, HttpStatus.OK);
}
}
Within my application.yml I have configured the context path for the application:
server:
contextPath: /testctx
My test for this controller looks as follows:
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = MyController.class, secure=false)
public class MyControllerTest {
#Autowired
private MyRepository repositoryMock;
#Autowired
private MockMvc mvc;
#Test
public void testGet() throws Exception {
MyEntity entity = new MyEntity();
entity.setId(10L);
when(repositoryMock.findOne(10L)).thenReturn(entity);
MockHttpServletResponse response = this.mvc.perform(
MockMvcRequestBuilders.get("/testctx/myentity/10"))
.andReturn().getResponse();
assertEquals(response.getStatus(), 200);
}
#TestConfiguration
public static class TestConfig {
#Bean
MyRepository mockRepo() {
return mock(MyRepository.class);
}
}
}
This test fails since the status code is 404 for the call. If I call /myentity/10 it works. Unfortunately the rest call is initiated by a CDC-Test-Framework (pact) so I cannot change the requested path (containing the context path /testctx). So is there a way to tell spring boot test to start the rest endpoint with a defined context path also during testing?
You could try:
#WebMvcTest(controllers = {MyController.class})
#TestPropertySource(locations="classpath:application.properties")
class MyControllerTest {
#Autowired
protected MockMvc mockMvc;
#Value("${server.servlet.context-path}")
private String contextPath;
#BeforeEach
void setUp() {
assertThat(contextPath).isNotBlank();
((MockServletContext) mockMvc.getDispatcherServlet().getServletContext()).setContextPath(contextPath);
}
protected MockHttpServletRequestBuilder createGetRequest(String request) {
return get(contextPath + request).contextPath(contextPath)...
}

Using autowired dependencies with certain mock dependency in Spring4

I have a rest resource for signup and login. both in a controller class. the controller class has a dependency to a service class with the business logic. the service class has further dependencies. cause i use an embedded db for testing, i want to use the real dependencies of my app instead to mock them with something like #injectmock #mock. there is only one certain dependency i have to mock. its the dependency for sending emails after a signup process. how to write test cases with #autowired function and one certain mock dependency for email notification?
#Controller
public class AccountCommandsController {
#Autowired
private LogoutService service;
#RequestMapping(value = "/rest/login", method = RequestMethod.POST)
public ResponseEntity login(#RequestBody Account account) {
AccountLoginEvent accountLoginEvent = service.loginAccount(new RequestAccountLoginEvent(account.getEmailAddress(), account.getPassword()));
if (accountLoginEvent.isLoginGranted()) {
return new ResponseEntity(HttpStatus.ACCEPTED);
} else {
return new ResponseEntity(HttpStatus.UNAUTHORIZED);
}
}
#RequestMapping(value = "/rest/signup", method = RequestMethod.POST)
public ResponseEntity signup(#RequestBody Account account) {
AccountSignupEvent signedupEvent = service.signupAccount(new RequestAccountSignupEvent(account.getEmailAddress(), account.getPassword()));
if (signedupEvent.isSignupSuccess()) {
return new ResponseEntity(HttpStatus.ACCEPTED);
} else if (signedupEvent.isDuplicateEmailAddress()) {
return new ResponseEntity(HttpStatus.CONFLICT);
} else if (signedupEvent.isNoSignupMailSent()) {
return new ResponseEntity(HttpStatus.SERVICE_UNAVAILABLE);
} else {
return new ResponseEntity(HttpStatus.FORBIDDEN);
}
}
}
#Service
public class LogoutService {
#Autowired
private AccountsRepository accountsRepository;
#Autowired
private MailService mailService;
#Autowired
private HashService hashService;
public AccountSignupEvent signupAccount(RequestAccountSignupEvent signupEvent) {
if (accountsRepository.existEmailAddress(signupEvent.getEmailAddress())) {
return AccountSignupEvent.duplicateEmailAddress();
}
Account newAccount = new Account();
newAccount.setCreated(new Date());
newAccount.setModified(new Date());
newAccount.setEmailAddress(signupEvent.getEmailAddress());
newAccount.setPassword(signupEvent.getPassword());
newAccount.setVerificationHash(hashService.getUniqueVerificationHash());
SignupMailEvent mailSentEvent = mailService.sendSignupMail(new RequestSignupMailEvent(newAccount));
if (!mailSentEvent.isMailSent()) {
return AccountSignupEvent.noMailSent();
}
Account persistedAccount = accountsRepository.persist(newAccount);
return AccountSignupEvent.accountCreated(persistedAccount);
}
public AccountLoginEvent loginAccount(RequestAccountLoginEvent loginEvent) {
if (accountsRepository.existLogin(loginEvent.getEmailAddress(), loginEvent.getPassword())) {
return AccountLoginEvent.granted();
}
return AccountLoginEvent.denied();
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = TestConfiguration.class)
#Transactional
#TransactionConfiguration(defaultRollback = true)
public class LogoutTest {
private MockMvc mockMvc;
#Autowired
private AccountCommandsController controller;
#Before
public void setup() {
mockMvc = standaloneSetup(controller).build();
}
#Test
public void signupNoMail() throws Exception {
doReturn(AccountSignupEvent.noMailSent()).when(service).signupAccount(any(RequestAccountSignupEvent.class));
// when(controller.service.signupAccount(any(RequestAccountSignupEvent.class))).thenReturn(AccountSignupEvent.noMailSent());
mockMvc.perform(post("/rest/signup")
.content(new Gson().toJson(new Account(UUID.randomUUID().toString(), UUID.randomUUID().toString())))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isServiceUnavailable());
}
}
I hope you see the problem. Every dependency works fine instead mailservice. I dont want to use #injectmock and #mock with MockitoAnnotations.initMocks(this); in my test file, because of the neccessary to provide for all dependencies mocks.
if your dependencies are running and you have a configuration class where you have defined the endpoint, you can use ConfigurableApplicationContext class, something like this:
public class test {
private static ConfigurableApplicationContext appContext;
private LogoutService service;
#AfterClass
public static void destroy() {
appContext.close();
}
#Before
public void setup() {
appContext = new AnnotationConfigApplicationContext(YourClassConfig.class);
service = appContext.getBean(LogoutService.class);
}
#Test
public void beansAreCreated() {
assertNotNull(service);
}
}
Or you can re-write your endpoint with a configuration class and you can use WireMock (http://wiremock.org) to emulate your dependency with real data, this should be something like this:
public class test {
#Rule
public WireMockRule wireMockRule = new WireMockRule(15000);
private static ConfigurableApplicationContext appContext;
private LogoutService service;
private static String serviceMockUrl;
#AfterClass
public static void destroy() {
appContext.close();
}
#Before
public void setup() {
serviceMockUrl = "http://localhost:" + wireMockRule.port();
appContext = new AnnotationConfigApplicationContext(TestConfig.class);
stubFor(get(urlEqualTo("urlToRequest")).
willReturn(aResponse().
withStatus(SC_OK).
withBody(createJsonArray("MapWithYourData").
withHeader("Content-Type", "application/json")));
service = appContext.getBean(LogoutService.class);
}
#Test
public void beansAreCreated() {
assertNotNull(service);
}
#Configuration
static class TestConfig {
#Bean
public PropertyPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertyPlaceholderConfigurer() {{
setProperties(new Properties() {{
setProperty("service.url", serviceMockUrl);
}});
}};
}
}
}
I hope this help you.
What you are trying to do is easily implemented using Spring Profiles.
On way to achieve it is the following:
#Configuration
public class TestConfiguration {
//this is the real mail service
#Bean
public MailService mailService() {
return new MailService(); //or whatever other bean creation logic you are using
}
//whatever else
}
#Configuration
#Profile("mockMail")
public class MockMailServiceConfig {
#Bean
#Primary
public MailService mockMailService() {
return mock(MailService.class);
}
}
Your test class would then look like:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = TestConfiguration.class)
#Transactional
#TransactionConfiguration(defaultRollback = true)
#ActiveProfiles("mockMail")
public class LogoutTest {
//do your testing
}
Note the use of #Primary in MockMailServiceConfig. I opted for this way since it wouldn't require you to introduce profiles anywhere else if you are not already using them. #Primary tells spring to use that specific bean if multiple candidates are available (in this case there is the real mail service and the mock service)

Categories

Resources