How to handle objects created within the method under test - spring

I have the following model classes:
#Data
public class Address {
private String street;
private int number;
}
#Data
public class Person {
private String name;
private Address address;
}
and the following services:
#Service
public class MyService {
private final OtherService otherService;
public MyService(OtherService otherService) {
this.otherService = otherService;
}
public void create() {
Person myPerson = new Person();
myPerson.setName("John");
otherService.synchronize(myPerson);
myPerson.getAddress().setNumber(12);
}
}
#Service
public class OtherService {
public void synchronize(Person person) {
Address address = new Address();
address.setStreet("sample street");
address.setNumber(123);
person.setAddress(address);
}
}
I want to write a unit test for MyService. This is the not working version of the test:
#ExtendWith(SpringExtension.class)
class MyServiceTest {
#Mock OtherService otherService;
#InjectMocks MyService myService;
#Test
void test_create() {
// GIVEN
doNothing().when(otherService).synchronize(any(Person.class));
// WHEN
myService.create();
// THEN
verify(otherService).synchronize(any());
}
}
This fails because the myPerson object is created within the method being tested and therefore I get a NullPointerException when running the test. How could I deal with this issue? should I capture the value passed to the otherService?

There's a little complexity but it's not bad. Replace your doNothing call with something like this:
Mockito.doAnswer(
new Answer<Void>() {
public Void answer(InvocationOnMock invocation) throws Exception {
Person arg = invocation.getArgument(0);
arg.setAddress(new Address());
return;
}
}).when(otherService).synchronize(any(Person.class));

Related

Is it possible to invoke mocked object's method in constructor?

I have, for example, these classes with Spring Boot. I try to do a REST API without a database and wieh files as data. The data files are like this:
{
"persons": [
{ "firstName":"John", "lastName":"Boyd", "address":"1509 Culver St", "city":"Culver", "zip":"97451", "phone":"841-874-6512", "email":"jaboyd#email.com" },
{ "firstName":"Jacob", "lastName":"Boyd", "address":"1509 Culver St", "city":"Culver", "zip":"97451", "phone":"841-874-6513", "email":"drk#email.com" }
] }
#Repository
public class PersonRepository {
private List<Person> persons;
private DataLoaderService loaderService;
#Autowired
public PersonRepository(DataLoaderService loaderService){
persons= loaderService.convertJsonToPojo("Persons",Person.class);
}
public List<Person> getAll(){
return persons;
}
}
#Service
public class DataLoaderService{
private JsonFileService jsonFileService;
private ObjectMapper mapper
#Autowired
public DataLoaderService(JsonFileService jsonFileService,ObjectMapper mapper){
this.JsonFileService =jsonFileService;
this.mapper=mapper;
}
public <T> List<T> convertJsonToPojo (String nodeName,Class <T>
classOfT){
}
}
So, I have a file. How can I read to transform to a list of Pojo?
When I want to mock the test method getAll(), my list size is 0. The mock doesn't give me values because I think the problem is that I initialized the value in the constructor. Here is my test:
#ExtendWith(MockitoExtension.class)
public class PersonRepositoryTest {
PersonRepository repository;
#Mock
private DataLoaderService loaderService;
#BeforeEach
public void setUp() {
repository = new PersonRepository(loaderService);
}
#Test
public void getAllPersonnesInConstructor() {
List<Person> mockedList = Arrays.asList(
new Person("Paul","Moes","1", "7777", "adresse tour", "Chicago", "pauln#gmail.com"),
new Person("Eleson","Moc","2", "77777", "ddkdkd", "New York", "eleson#gmail.com")
);
doReturn(mockedList).when(loaderService).convertJsonToPojo("persons",Person.class);
List<Person> persons = repository.getAll();
assertEquals(2,persons.size(),"Expected list size is 2");
assertEquals(persons,mockedList);
}
If i use #Spy, I have an error.
When I use method getAll() without initializing the variable persons in the constructor but in the method getAll, it is OK, like this:
public List<Person> getAll(){
this.persons=this.dataLoaderService.convertJsonToPojo("persons", Person.class);
log.debug("persons getALL repository" + persons);
return this.persons;
}
What can I do to test it?
Test a method which initializes a value in the constructor.

Entity listener can inject other Spring dependencies but not repository

I have this entity listener class:
#Component
public class AssignmentListener {
private KafkaService kafkaService;
private String topic;
private AssignmentMapper assignmentMapper;
private AttachmentRepository attachmentRepository;
#Autowired
public final void setKafkaService(KafkaService kafkaService) {
this.kafkaService = kafkaService;
}
#Autowired
public final void setTopic(
#Value("${topic}") String topic
) {
this.topic = topic;
}
#Autowired
public final void setAssignmentMapper(AssignmentMapper assignmentMapper) {
this.assignmentMapper = assignmentMapper;
}
#Autowired
public final void setAttachmentRepository(AttachmentRepository attachmentRepository) {
this.attachmentRepository = attachmentRepository;
}
#PostPersist
#PostUpdate
#Transactional("transactionManager")
#TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void postUpdate(Assignment assignment) {
var attachments = attachmentRepository.findAllByAssignmentId(assignment.getId());
var dto = assignmentMapper.mapToKafkaMessage(assignment);
dto.setAttachments(
attachments.stream()
.map(Attachment::getPath)
.collect(Collectors.toSet())
);
kafkaService.sendMessage(
topic,
dto
);
}
}
and it worked normally until adding this last field which is repository. All other dependencies were injected however no matter what I do this won't get injected. Just to mention this is happening in tests. Do you have any suggestion?

How to test GET request with body in Spring RestController?

I have a rest controller like this;
#RestController
#RequiredArgsConstructor
#RequestMapping(PO)
public class PoController {
private final PoService service;
#GetMapping(value = FILTER, produces = APPLICATION_JSON_VALUE)
public ResponseEntity<List<PoDTO>> filter(PoFilterCriteria poFilterCriteria) {
return ok().body(service.getPos(poFilterCriteria));
}
}
And I want to write an unit test for it but I couldn't achieve to mock the service to return list.
This is my poFilterCriteria model;
#Data
public class PoFilterCriteria {
private double hp;
private FilterOperationType hpOperationType;
private double attack;
private FilterOperationType attackOperationType;
private double defense;
private FilterOperationType defenseOperationType;
}
And this is my test;
#WebMvcTest(value = PoController.class)
class PoControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private PoService service;
private PoDTO poDTO;
private List<PoDTO> poDTOList;
#BeforeEach
void setUp() {
poDTOList = new ArrayList<>();
poDTO = new Po();
poDTOList.add(poDTO);
}
#Test
public void filter_success() throws Exception {
PoFilterCriteria poFilterCriteria= new PoFilterCriteria ();
poFilterCriteria.setAttack(40);
poFilterCriteria.setAttackOperationType(GT);
poFilterCriteria.setHp(49);
poFilterCriteria.setHpOperationType(EQ);
poFilterCriteria.setDefense(60);
poFilterCriteria.setDefenseOperationType(LT);
when(service.getPos(poFilterCriteria)).thenReturn(poDTOList);
mockMvc.perform(get(PO + FILTER)
.param("hp", String.valueOf(40))
.param("hpOperationType", String.valueOf(GT))
.param("attack", String.valueOf(49))
.param("attackOperationType", String.valueOf(EQ))
.param("defense", String.valueOf(60))
.param("defenseOperationType", String.valueOf(LT))
.contentType(APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json(new ObjectMapper().writeValueAsString(poDTOList)));
}
}
But the list that should return with size of 1 is returning empty.
What did I do wrong?
org.mockito.ArgumentMatchers#any(java.lang.Class)
when(service.getPos(any(PoFilterCriteria.class))).thenReturn(poDTOList);
or
org.mockito.ArgumentMatchers#same
when(service.getPos(same(poFilterCriteria))).thenReturn(poDTOList);

java.lang.ClassCastException: Entity A incompatible with Entity B

I'm trying to get proficient in generics in Java. I have some 100 entities that use the same findBy method in JPA interface. Almost all of them require a call to AwrSnapDetails so instead of adding
#ManyToOne private AwrSnapDetails awrSnapDetails; to each Entity, I've created a HelperEntity class and using #Embedded annotation. Now I have gotten to the point in coding where I can't figure out what I am doing wrong and how to go about resolving this error.
Entity
#Entity
public class AwrMemStats {
String description;
double begin_;
double end_;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
#Embedded
private HelperEntity helperEntity;
public AwrMemStats() {
}
public AwrMemStats(String description, double begin_, double end_, AwrSnapDetails awrSnapDetails) {
this.description = description;
this.begin_ = begin_;
this.end_ = end_;
HelperEntity h = new HelperEntity(awrSnapDetails);
}
// getters/setters removed for clarity
}
Embedded Entity
#Embeddable
public class HelperEntity implements Serializable{
private static final long serialVersionUID = 1L;
#ManyToOne
AwrSnapDetails awrSnapDetails;
public HelperEntity() {
}
public HelperEntity(AwrSnapDetails awrSnapDetails) {
super();
this.awrSnapDetails = awrSnapDetails;
}
public AwrSnapDetails getAwrSnapDetails() {
return awrSnapDetails;
}
public AwrSnapDetails setAwrSnapDetails(AwrSnapDetails awrSnapDetails) {
return this.awrSnapDetails = awrSnapDetails;
}
}
Service Class
#Service
public class HelperService<T> {
#Autowired
private HelperRepository<T> repository;
public void add(T entity) {
repository.save(entity);
}
public void add(List<T> entities) {
repository.saveAll(entities);
}
public T get(T entity) {
T t = repository.findByHelperEntityAwrSnapDetailsStartSnapIdAndHelperEntityAwrSnapDetailsInstanceDetailDbNameAndHelperEntityAwrSnapDetailsInstanceDetailDbId(
((HelperEntity) entity).getAwrSnapDetails().getStartSnapId(),
((HelperEntity) entity).getAwrSnapDetails().getInstanceDetail().getDbName(),
((HelperEntity) entity).getAwrSnapDetails().getInstanceDetail().getDbId());
//((AwrMemStats) entity).getHelperEntity().getAwrSnapDetails().getStartSnapId(),
//((AwrMemStats) entity).getHelperEntity().getAwrSnapDetails().getInstanceDetail().getDbName(),
//((AwrMemStats) entity).getHelperEntity().getAwrSnapDetails().getInstanceDetail().getDbId());
if (t!= null) {
return t;
}
return null;
}
}
Controller
#RestController
public class HelperController<T> {
#Autowired
private HelperService<T> service;
public void add(T entity) {
service.add(entity);
}
public void add(List<T> entities) {
service.add(entities);
}
public T get(T entity) {
return service.get(entity);
}
}
Execution
getAwrSnapDetails() initilized in HelperLoader
#Component
public class LoadAwrMemStats extends HelperLoader{
#Autowired
private HelperController<AwrMemStats> controller;
public void doThis() {
AwrMemStats profile = new AwrMemStats("a",1.0,1.0,getAwrSnapDetails());
AwrMemStats s = controller.get(profile);
ANd finally the ERROR message
Exception in thread "restartedMain" java.lang.reflect.InvocationTargetException
...
...
Caused by: java.lang.ClassCastException: net.mharoon.perfmon.awr.entities.AwrMemStats incompatible with net.mharoon.perfmon.awr.entities.HelperEntity
at net.mharoon.perfmon.awr.service.HelperService.get(HelperService.java:27)
at net.mharoon.perfmon.awr.controller.HelperController.get(HelperController.java:24)
...
...
Update this code works but only for given class AwrMemStats.
public List<T> get(T entity) {
List<T> ts = repository.findByHelperEntityAwrSnapDetailsStartSnapIdAndHelperEntityAwrSnapDetailsInstanceDetailDbIdAndHelperEntityAwrSnapDetailsInstanceDetailDbName(
//((HelperEntity) entity).getAwrSnapDetails().getStartSnapId(),
//((HelperEntity) entity).getAwrSnapDetails().getInstanceDetail().getDbName(),
//((HelperEntity) entity).getAwrSnapDetails().getInstanceDetail().getDbId());
((AwrMemStats) entity).getHelperEntity().getAwrSnapDetails().getStartSnapId(),
((AwrMemStats) entity).getHelperEntity().getAwrSnapDetails().getInstanceDetail().getDbId(),
((AwrMemStats) entity).getHelperEntity().getAwrSnapDetails().getInstanceDetail().getDbName());
if (!ts.isEmpty()) {
return ts;
}
return null;
}
The reason is because you are returning an Object that is not AwrMemStats and assigning it to AwrMemStats.
A simple work around is to replace
public T get(T entity)
with
public <T extends AwrMemStats> T get(T entity)
EDIT : Another solution (which is more generic) is..
replace
public class AwrMemStats
with
public class AwrMemStats extends HelperEntity
then replace
AwrMemStats s = controller.get(profile);
with
AwrMemStats s = (AwrMemStats) controller.get(profile);

PowerMockito verifyNew and verifyPrivate are mutually exclusive?

I have a class like this
#Component
public class TestClass {
public void testMethod(){
FinalClass f = new FinalClass("string");
somePrivateMethod(f.getSomeString());
}
private void somePrivateMethod(String s){
}
}
As you can see it has a public method and private method. In public method it is instantiating an instance of FinalClass, which is a class in some third party library and it is final. Lets say it is like this
public final class FinalClass {
private final String someString;
public FinalClass(final String s) {
someString = s;
}
public String getSomeString() {
return someString;
}
}
And Now I am writing unit test for my test class. Since I have to verify final classes and private methods, I am using powermockito. And this is how my test class looks like
#RunWith(PowerMockRunner.class)
#PrepareForTest({TestClass.class, FinalClass.class})
public class TestClassTest {
private TestClass testClass;
private FinalClass finalClass;
#Before
public void setUp() {
finalClass = PowerMockito.mock(FinalClass.class);
testClass = spy(new TestClass());
}
#Test
public void testSomething() throws Exception {
whenNew(FinalClass.class).withAnyArguments().thenReturn(finalClass);
testClass.testMethod();
verifyNew(FinalClass.class);
//verifyPrivate(testClass).invoke("testMethod");
}
}
It works fine. But the problem is the last two statements verifyNew and verifyPrivate are working mutually exclusively. I mean when I comment one of those(doesn't matter which), the test passes. But when both are enabled, the test fails
Does anyone have any idea why this is happening?

Resources