I am currently working on a Spring Boot application that allows users to save categories into a database. I can get my code to "work", however, I think it limits the amount of testing I can do, hence my question.
The controller receives a list of categories. The controller iterates over these categories, validates them and depending on whether they are valid, they are saved to a database. The controller finally returns a list of messages, such that the recipient can identify which category has been accepted or rejected, etc.
I have a list of model messages (List), which upon each iteration, the controller instantiates a new model message (new ModelMessage()) and eventually adds it to the List. Is there a way to inject a new ModelMessage upon each iteration or do I need to use the new keyword? If I do use the new keyword, I feel like this is limiting my testability/tightly coupling my controller to the model message.
The controller:
#PostMapping("/category")
public String saveCategoryModelToDatabase(#RequestBody CategoryModelWrapper categoryModelWrapper){
List<CategoryModel> categoryModelList = categoryModelWrapper.getCategoryModelList();
modelMessageList.clear();
for(CategoryModel categoryModel : categoryModelList){
//Resetting model
modelMessage = new ModelMessage(); //This tightly couples my method to the ModelMessage class, which is bad for testing?
//#Autowired modelMessage; <-- something like this? Inject a new ModelMessage with each iteration.
modelMessage.setName(categoryModel.getName());
//Resetting categoryModelErrors
Errors categoryModelErrors = new BeanPropertyBindingResult(categoryModel, "categoryModel");
categoryModelValidator.validate(categoryModel, categoryModelErrors);
if(categoryModelErrors.hasErrors()){
modelMessage.setStatus(ModelMessageStatusEnum.REJECTED);
modelMessage.setReason(MODEL_MESSAGE_0004);
}
if(categoryModelService.save(categoryModel)){
modelMessage.setStatus(ModelMessageStatusEnum.ACCEPTED);
}
else{
modelMessage.setStatus(ModelMessageStatusEnum.REJECTED);
modelMessage.setReason(MODEL_MESSAGE_0005);
}
modelMessageList.add(modelMessage);
}
return gson.toJson(modelMessageList);
}
An example of the response to the recipient:
[{"name":"Arts","status":"ACCEPTED"},{"name":"Business","status":"ACCEPTED"},{"name":"Gaming","status":"ACCEPTED"},{"name":"Deals","status":"REJECTED","reason":"Category rejected because of an unexpected exception, i.e. possibly due to duplicate keys."}]
Thanks for any help :)
You could use the ApplicationContext, assuming you have access to it, as a factory for ModelMessage. But, is that really necessary?
I think you can create new ModelMessages in your Controller, it's only a data object and not a service bean.
A junit can check the result of the method.
But if you really want to use Spring, I would look at the FactoryBean...
Example:
public class ModelMessage {
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
#Component
public class ModelMessageFactory implements FactoryBean<ModelMessage> {
#Override
public ModelMessage getObject() throws Exception {
return new ModelMessage();
}
#Override
public Class<?> getObjectType() {
return ModelMessage.class;
}
}
#RunWith(SpringRunner.class)
#SpringBootTest
#SpringJUnitConfig
public class ModelMessageFactoryTest {
#Autowired
private ModelMessageFactory messageFactory;
#Test
public void testGetObject() throws Exception {
assertNotNull("Factory is null", messageFactory);
IModelMessage modelMessage1 = messageFactory.getObject();
IModelMessage modelMessage2 = messageFactory.getObject();
assertNotEquals("error object is equal", System.identityHashCode(modelMessage1),
System.identityHashCode(modelMessage2));
}
#Test
public void testGetObjectType() throws Exception {
assertEquals(ModelMessage.class, messageFactory.getObjectType());
}
}
Related
I'm new to Java development and Spring/Springboot. I was tasked to create a cron job that would execute a query to a database. Although, my problem would focus on calling the function since it's already existing in the project. I'm still quite confused and learning about Dependency Injection, Design Patterns and Spring. I tried to resolve this myself, but it's taking a while now -- so I figured to ask while I'm also trying to figure it out just to save time in case they ask me for a deadline. Thank you so much in advance!
This is how the programs are generally structured:
QueryConfig.java
This is a new and only file I created. I was able to make the cron job work, as I tried to put a logger inside runQuery() and it was able to run every 5 minutes as per the configuration file.
#RefreshScope
#Component
public class QueryConfig {
#Value("${cron.job.query}")
private String sql;
String name = "Bob";
StudentMgtApiDelegate delegate = new StudentMgtApiDelegateImpl();
#Scheduled(cron = "${cron.job.schedule}", zone = "${cron.job.timezone}")
public void runQuery() {
delegate.retrieveStudents(name);
}
}
StudentMgtApiDelegateImpl.java
Please also note that this is just a representation of the code since I cannot share the actual. I'll try my best to make it as close to the real implementation. There are 3 methods for the API, but I just want to call the retrieveStudents().
#Component
public class StudentMgtApiDelegateImpl implements StudentMgtApiDelegate {
#Autowired
private StudentFacade studentFacade;
#Override
public ResponseEntity<List<Student>> retrieveStudents(String name) {
return ResponseEntity.ok(studentFacade.retrieveStudents(
...
));
}
#Override
public ResponseEntity<StudentDetails> retrieveStudentDetails(String name...) {
return ResponseEntity.ok(studentFacade.retrieveStudentDetails(
...
));
}
#Override
public ResponseEntity<List<CountBreakdown>> retrieveStudentCounts(String name) {
return ResponseEntity.ok(studentFacade.studentCountsRetrieve(
...
));
}
}
StudentFacade.java
public class StudentFacade {
private Function<DataWrapper<StudentParams<String>>, List<Student>> studentListRetrieveFn;
private Function<DataWrapper<StudentParams<String>>, StudentDetails> studentDetailsRetrieveFn;
private Function<DataWrapper<StudentRetrieveCriteria>, List<CountBreakdown>> studentCountsRetrieveFn;
public StudentFacade(Function<DataWrapper<StudentParams<String>>, List<Student>> studentListRetrieveFn, Function<DataWrapper<StudentParams<String>>, StudentDetails> studentDetailsRetrieveFn, Function<DataWrapper<StudentRetrieveCriteria>, List<CountBreakdown>> studentCountsRetrieveFn) {
this.studentListRetrieveFn = studentListRetrieveFn;
this.studentDetailsRetrieveFn = studentDetailsRetrieveFn;
this.studentCountsRetrieveFn = studentCountsRetrieveFn;
}
public List<Student> retrieveStudents(DataWrapper<StudentParams<String>> wrapper) {
return Optional.ofNullable(wrapper).map(studentListRetrieveFn).orElse(null);
}
public StudentDetails retrieveStudentDetails(DataWrapper<StudentParams<String>> wrapper) {
return Optional.ofNullable(wrapper).map(studentDetailsRetrieveFn).orElse(null);
}
public List<CountBreakdown> studentCountsRetrieve(DataWrapper<StudentRetrieveCriteria> wrapper) {
return Optional.ofNullable(wrapper).map(studentCountsRetrieveFn).orElse(null);
}
}
I apologize in advance for the many code omissions and I know some parameters won't match and make sense. But as of the current implementation in my QueryConfig.java, I am encountering this error:
[scheduling-1] ERROR o.s.s.s.TaskUtils$LoggingErrorHandler - Unexpected error occurred in scheduled task
java.lang.NullPointerException: null
I tried to debug and see the value of delegate inside the QueryConfig.java, and it has a studentFacade that is null.
Autowire the StudentMgtApiDelegate; do not construct it manually.
#Autowired
StudentMgtApiDelegate delegate;
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.
I am creating a project which will respond to collect multiple bean object, save it to the database and return the status of the transaction. There can be multiple objects that can be sent from the client. For each object, they are having separate database thus separate controller.
So I planned to create a framework that can accept multiple objects from multiple controllers and send only one centralized object. But I am not sure how to use a centralized object as a return type in the controller(currently I returned them as Object). Below is my code:
Controller:
#RestController
#RequestMapping("/stat/player")
public class PlayerController {
#Autowired
private StatService<PlayerValue> statPlayer;
#RequestMapping("/number/{number}")
public Object findByNumber(#PathVariable String number) { // Here returning Object seem odd
return statPlayer.findByNumber(number);
}
}
Service:
#Service
#Transactional(isolation = Isolation.READ_COMMITTED)
public class PlayerServiceImpl implements StatService<PlayerValue> {
#Autowired
private PlayerRepository repository;
#Override
public PlayerValue findByNumber(String number) {
Optional<PlayerEntity> numberValue = repository.findByNumber(number);
return numberValue.map(PlayerEntity::toValue).orElse(null);
}
}
In service I returned the PlayerValue object but I want to wrap this object into a centralized bean ResponseValue. I created an aspect for that
#Aspect
#Component
public class Converter {
private static final Logger LOG = LoggerFactory.getLogger(Converter.class);
#Pointcut("within(#org.springframework.web.bind.annotation.RestController *)")
public void restControllerClassMethod() {}
private <T> ResponseValue<T> convert(List<T> results) {
String message = results.isEmpty() ? "No result found" : ResponseValueStatus.OK.toString();
return new ResponseValue<>(ResponseValueStatus.OK, message, results);
}
#Around("restControllerClassMethod()")
#SuppressWarnings("unchecked")
public <T> ResponseValue<T> convert(ProceedingJoinPoint joinPoint) {
ResponseValue value;
try {
Object findObject = joinPoint.proceed();
List<Object> objects = toList(findObject);
value = convert(objects);
} catch (NullPointerException e) {
throw new StatException(String.format("Exception thrown from %s from %s method with parameter %s", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(), joinPoint.getArgs()[0].toString()));
//this exception will go in a controller advice and create a response value with this message
} catch (Throwable e) {
LOG.error("Exception occurred while converting the object", e);
throw new StatException(String.format("Exception thrown from %s from %s method with parameter %s with exception message %s", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(), joinPoint.getArgs()[0].toString(), e.getMessage()));
}
return value;
}
private List<Object> toList(Object findObject) {
List<Object> objects = new ArrayList<>();
if (findObject instanceof List) {
((List) findObject).forEach(item -> objects.add(findObject));
} else {
objects.add(findObject);
}
return objects;
}
}
To sum up, There could be multiple entity similar to PlayerValue. I need a way to return the result in a centralized bean. Above process work, BUT for this I have to give return type as Object in Controller. Does anybody has an idea how can I use return type as List or T in controller. Also I know it can be done by implementing a ValueConverter interface, but this conversion is straightforward. So it would be nice if any other developer don't have to implement the ValueConverter everytime he want to add a different controller.
Also feel free to review the implementation and let me know if anyone has some alternative idea or some comments.
Note: I reduce a lot of code in the question so that it can be easier to understandable without understanding the actual requirement context. Please do let me know if anyone need more info.
After some research I came across to a better design solution for the framework (but of course with flaws) to achieve conversion to a centralized bean for multiple domain objects is to use a marker interface.
Marker interface can provide a centralized type for all the bean. The main rule need to be followed by the client is to implement that marker interface. So the basic solution is
Marker interface:
public interface StateResponseValue<T> {
}
Implement the interface in all the bean.
public class PlayerValue implements StateResponseValue<PlayerValue> {
}
public class ResponseValue<T> implements StateResponseValue<T> {
//fields and their getter and setter
}
Change the return type in service and controller.
public interface StatService<T> {
StateResponseValue<T> findByNumber(String number);
}
Change the return type in controller
#RestController
#RequestMapping("/stat/player")
public class PlayerController {
#Autowired
private StatService<PlayerValue> statPlayer;
#RequestMapping("/number/{number}")
public StateResponseValue<T> findByNumber(#PathVariable String number) { // Here returning Object seem odd
return statPlayer.findByNumber(number);
}
}
Note: The main drawback I feel is that whenever we want to access the field client need to explicitly cast the object to ResponseValue which is still pretty ugly.
What if you create an AbstractStatController which is generic ?
Generic interface StatService:
public interface StatService<T> {
T findByNumber(String number);
}
Generic abstract class AbstractStatController:
public abstract class AbstractStatController<T> {
abstract StatService<T> getStatService();
#RequestMapping("/number/{number}")
public T findByNumber(#PathVariable String number) {
return getStatService().findByNumber(number);
}
}
Concrete class PlayerController:
#RestController
#RequestMapping("/stat/player")
public class PlayerController extends AbstractStatController<Player> {
private final PlayerService playerService;
public PlayerController(PlayerService playerService) {
this.playerService = playerService;
}
#Override
StatService<Player> getStatService() {
return playerService;
}
}
I am currently using Play 2.3 and I have to deal with similar URL mappings:
GET /api/companyId/employeeId/tasks
GET /api/companyId/employeeId/timesheets
etc.
In every GET I need to perform similar logic:
public Promise<Result> getEmployeeTimesheets(Long companyId, Long employeeId) {
return promise(() -> {
if (!companyRepository.one(companyId).isPresent()) {
return notFound("Company doesn't exist");
}
if (!employeeRepository.one(employeeId).isPresent()) {
return notFound("Employee doesn't exist");
}
if (!employeeRepository.employeeWorksForCompany(companyId, employeeId)) {
return forbidden("Employee doesn't work for this company");
}
// some actual logic here
});
}
This code repeats over and over again. So far I used plain old inheritance and moved that repeating code into the parent controller class. It gets the job done, but it certainly isn't perfect solution (because I have to invoke parent method and inspect results manually in every controller action).
Is there some more declarative approach in Play that would automatically handle fragment of URL (/api/companyId/employeeId in our case) and either delegate the execution to an appropriate controller, or return an error response (for example 404 - Not Found).
You said you are calling the method again and again in each controller function instead you can use #With annotation.For ex
create a class CheckUrl.java
public class CheckUrl extends play.mvc.Action.Simple {
public F.Promise<SimpleResult> call(Http.Context ctx) throws Throwable {
String host = request().uri();
if (condition one satisfied) {
return F.Promise.pure(redirect("/someurl"));
}else if (condition two satisfied){
return F.Promise.pure(redirect(controllers.routes.SomeController.index()));
}
}
Place #With(CheckUrl.class) in class to apply to all its function.
#With(CheckUrl.class)
public class MyController extends Controller {
}
and for a particular function
public class MyController extends Controller {
#With(CheckUrl.class)
public static Result index() {
}
}
In the above cases CheckUrl.java is invoked before function in a controller
I'm trying to unit test an existing ZK controller and I want to find a way to handle a call like the following while unit testing my Controller,
Sessions.getCurrent().setAttribute("from", from.getValue());
I'd be happy to either replacing the offending code, or find a way around it for the unit test. My goal is testability by dealing with the NullPointerException
My test is simple (it's not too bad a place to start...)
#Test
public void zkControllerDoesMockingInitialisedSuccessfully() throws Exception {
T2TripBigDaoInterface tripBigDao = createMock(T2TripBigDao.class);
ZkFieldValidator fieldValidator = createMock(ZkTextFieldValidator.class);
FieldRangeValidator rangeValidator = createMock(DefaultFieldRangeValidator.class);
TripController controller = new TripController(tripBigDao, fieldValidator, rangeValidator);
replay(tripBigDao, fieldValidator, rangeValidator);
controller.onClick$getTrips(new Event("getTrips"));
verify(tripBigDao, fieldValidator, rangeValidator);
// Test purpose: Just get a unit test of the controller running to start with....
}
Extract of the controller:
public class TripController extends GenericForwardComposer {
....
public void onClick$getTrips(Event event) throws Exception {
Sessions.getCurrent().setAttribute("from", from.getValue());
Sessions.getCurrent().setAttribute("to", to.getValue());
....
}
....
Extract of the stack trace:
java.lang.NullPointerException
at com.t2.webservice.controller.alert.TripController.onClick$getTrips(TripController.java:72)
at com.t2.webservice.controller.alert.TripControllerTest.zkControllerDoesMockingInitialisedSuccessfully(TripControllerTest.java:45)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
This is one of the things I dislike most about ZK: their use of singletons and the impact that has on testability.
What I end up doing is removing any references to their singletons (Sessions, Executions, Selectors) from my controllers. In normal operation these singletons get used, but in tests they can be mocked out.
How you go about this is up to you, I still haven't found a pattern I'm in love with.
Here's one idea..
public class TripController extends GenericForwardComposer {
private final TripSessionManager tripSessionManager;
public TripController() {
// ZK calls the default constructor
this(new ZKTripSessionManager());
}
protected TripController(TripSessionManager tripSessionManager) {
this.tripSessionManager = tripSessionManager;
}
public void onClick$getTrips(Event event) throws Exception {
tripSessionManager.setTo(to.getValue());
tripSessionManager.setFrom(from.getValue());
}
}
Your TripSessionManager would then look like this..
public interface TripSessionManager {
void setTo(String to);
void setFrom(String from);
}
With the default ZK implementation relying on the Sessions singleton..
public ZKTripSessionManager implements TripSessionManager {
public void setTo(String to) {
setAttribute("to", to);
}
public void setFrom(String from) {
setAttribute("from", from);
}
private void setAttribute(String name, String value) {
// only valid if called in a ZK managed thread
Sessions.getCurrent().setAttribute(name, value);
}
}
By abstracting out the implementation, you can test your controller with a mock TripSessionManager..
#Test
public void test() {
TripSessionManager mockTripSessionManager = mock(TripSessionManager);
when(mockTripSessionManager.setTo(anyString()).thenAnswer(...);
when(mockTripSessionManager.setFrom(anyString()).thenAnswer(...);
TripController controller = new TripController(mockTripSessionManager);
}
You could also imagine different ways of managing these new dependencies (eg: avoid new ZKTripSessionManager()) using dependency injection frameworks like Spring or Guice.