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
#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.
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?
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");
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)...
}
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)