I have following two methods which i want to test
public class Orders {
private final LambdaLogger logger;
private final DynamoDBMapper dynamoDBMapper;
public Orders(LambdaLogger logger, AmazonDynamoDB amazonDynamoDB){
this.logger = logger;
this.dynamoDBMapper = new DynamoDBMapper(amazonDynamoDB);
}
public List<Orders> getOrders(){
logger.log("getting all orders");
DynamoDBScanExpression scanExpression = new DynamoDBScanExpression()
.withProjectionExpression("OrderId");
logger.log("Expression created");
PaginatedScanList<Orders> scan = dynamoDBMapper.scan(Orders.class, scanExpression);
return scan.stream()
.collect(Collectors.toList());
}
}
Now, I want to do testing using Mockito for this class. There are couple of things that I am confuse (or unable to get working).
First, DynamoDBMapper is being created using amazonDynamoDBClient. So if in my class i Mock AmazonDynamoDB, how the dynamoDBMapper would get created?
How would I test that my function is actually setting projection right?
How would i test on paginatedScanList?
It is violation of Dependency Injection principle that does not allow you to create a unit test.
Orders should not create any objects it should receive them as dependencies through constructor or setter methods. E.g. you could pass DynamoDBScanExpressionFactory and DynamoDBMapper to Orders' constructor. Then you would be able to mock them with mockito.
Related
Hi have read a lot about this but can't come to a conclusion about the best way to test a method that is dependent on other method call results to perform its actions.
Some of the questions I've read include:
Testing methods that depend on each other
Unit testing a method that calls other methods
Unit testing a method that calls another method
Some of the answers sugest that we should only test the methods that perform only one action and then test the method that call this methods for conditional behaviuour (for example, verifying if a given method was called or not) and that's fine, I get it, but I'm struggling with other scenario.
I have a service with a REST api.
The controller has a create method that receives a DTO and calls the Service class create method with this argument (DTO).
I'm trying to practice TDD and for this I use this project I'm building without a database.
The code is as follows:
#Service
public class EntityService implements FilteringInterface {
private MemoryDatabase db = MemoryDatabase.getInstance();
//Create method called from controller: receives DTO to create a new
//Entity after validating that it's name and code parameters are unique
public EntityDTO create(EntityDTO dto) throws Exception {
validateUniqueFields(dto);
Entity entity = Entity.toEntity(dto, "id1"); //maps DTO to Entity object
db.add(entity);
return new EntityDTO.Builder(entity);//maps entity to DTO
}
public void validateUniqueFields(EntityDTO dto) throws Exception {
Set<Entity> foundEntities = filterEntityByNameOrCode(dto.getName(),
dto.getCode(), db.getEntities());
if (!foundEntities.isEmpty()) {
throw new Exception("Already exists");
}
}
}
This is the interface with methods reused by other service classes:
public interface FilteringInterface {
default Set<Entity> filterEntityByNameOrCode(String name, String code, Set<Entity> list) {
return list.stream().filter(e -> e.getSiteId().equals(siteId)
&& (e.getName().equals(name)
|| e.getCode().equals(code))).collect(Collectors.toSet());
}
default Optional<Entity> filterEntityById(String id, Set<Entity> list) {
return list.stream().filter(e -> e.getId().equals(id)).findAny();
};
}
So, I'm testing this service class and I need to test the create() method because it can have different behaviors:
If the received DTO has a name that already exists on the list of entities -> throws Exception
If the received DTO has a code that already exists on the list of entities -> throws Exception
If the received DTO has a name and a code that already exists on the list of entities -> throws Exception
If name and code are different, than everything is ok, and creates the entity -> adds the entity to the existing list - > converts the entity to DTO and retrieves it.
Problem:
To test any of the scenarios, suppose, scenario 1: I need to make the filterEntityByNameOrCode() method return a list with an Entity that has the same name as the Entity I'm trying to create. This method is called inside validateUniqueFields() method.
Problem is: I can't call mockito when() for any of this methods because, for that, I would have to mock the service class, which is the class that I'm testing and, thus, it's wrong approach.
I've also read that using Spy for this is also wrong approach.
So, where thus that leaves me?
Also: if this code is not the correct aprocah, and thats why
it can't be correctly tested, than, whats should the correct approach be?
This service will have other methods (delete, update, etc.). All of this methods will make use of the FilteringInterface as well, so I will have the same problems.
What is the correct way of testing a service class?
I would apply an DI pattern in your service, in order to mock and control the db variable.
#Service
public class EntityService implements FilteringInterface {
private Persistence db;
public EntityService(Persistence db) {
this.db = db;
}
}
After that, you will be able to add entities to Set accordingly to your scenarios
#ExtendWith(MockitoExtension.class)
class EntityServiceTest {
#Mock
private Persistence persistence;
#InjectMocks
private EntityService entityService;
#BeforeEach
void before() {
final Set<Entity> existentEntity = Set.of(new Entity(1L,1L, "name", "code"));
when(persistence.getEntities()).thenReturn(existentEntity);
}
#Test
void shouldThrowWhenNameAlreadyExists() {
final EntityDTO dto = new EntityDTO(1L, "name", "anything");
assertThrows(RuntimeException.class, () -> entityService.create(dto));
}
#Test
void shouldThrowWhenCodeAlreadyExists() {
final EntityDTO dto = new EntityDTO(1L, "anything", "code");
assertThrows(RuntimeException.class, () -> entityService.create(dto));
}
#Test
void shouldThrowWhenNameAndCodeAlreadyExists() {
final EntityDTO dto = new EntityDTO(1L, "name", "code");
assertThrows(RuntimeException.class, () -> entityService.create(dto));
}
#Test
void shouldNotThrowWhenUnique() {
final EntityDTO dto = new EntityDTO(1L, "diff", "diff");
final EntityDTO entityDTO = entityService.create(dto);
assertNotNull(entityDTO);
}
}
I'm trying to create a unittest for the method below (myHostClient), but I'm having some problems with it:
MyClass.java
import com.abc.def.ServiceBuilder
public class MyClass {
#Value("${materialLocation}")
private String materialValue
private static final SERVICEBUILDER = new ServiceBuilder()
#Bean public MyHostServiceClient myHostClient(
#Value(${qualifier_one}) final String qualiferOne,
#Value(${use_service}) final boolean useService) {
if(useService) {
return SERVICEBUILDER
.remote(MyHostServiceClient.class)
.withConfig(qualifierOne)
.withCall(new CallerAttach(Caller.retro(defaultStrategy())), // Error Line2 Here
new SigningVisitor(new CredentialsProvider(materialValue))),
call -> call.doSomeStuff(StuffObject.getStuffInstance()))
.makeClient();
}
#Bean DefaultStrategy<Object> defaultStrategy() {
final int valueA = 1;
final int valueB = 2;
return new DoSomeThingsBuilder()
.retry(valueA)
.doSomethingElse(valueB)
.create();
}
}
And here is my latest unsuccessful attempt at writing a unittest for it:
MyClassTest.java
import org.mockito.Mock
import static org.mockito.Mockito.times
public class MyClassTest {
#Mock
private SERVICEBUILDER serviceBuilder;
private MyClass myClass;
private String qualifierOne = "pass"
#BeforeEach
void setUp() {
myClass = new MyClass();
}
#Test
public void test_myHostClient() {
boolean useService = true;
final MyHostServiceClient result = myclass.myHostClient(qualifierOne, useService); // Error Line1 here
verify(serviceBuilder, times(1));
}
}
I have been trying to mock SERVICEBUILDER and verify that the mocked object is called one time but no luck so far. Right now I'm getting this error:
IllegalArgumentException: Material Name cannot be null
And it points to these lines in my code.
In the Test:
final MyHostServiceClient result = myclass.myHostClient(qualifierOne, useService);
Which points to this line in the module:
.withCall(new CallerAttach(Caller.retro(defaultStrategy())),
Anyone know how I can fix my unittest or write a working one from scratch?
I would say the design of MyClass is quite wrong because it looks like a Spring configuration but apparently it's not. If it is really supposed to be a configuration then I wouldn't even test it like this because it would rather be an integration test. Of course, even in integration tests you can mock dependencies. But the test itself would run differently and you would have to spin up a suitable Spring context, etc.
So given the above, I would rather make MyClass some sort of MyHostServiceClientFactory with removing all of the Spring annotations and then fix the following problems in your code.
SERVICEBUILDER is hardcoded.
SERVICEBUILDER is static final and its value is hardcoded into MyClass. You will not be able to reassign that field with the mocked version. It can still be final but not static then and it's better to use dependency injection here by passing the value through the MyClass constructor.
SERVICEBUILDER will still be not mocked even if you fix the above.
To really mock SERVICEBUILDER by using the #Mock annotation in the test you should enable Mockito annotations.
If you are using JUnit5 then you should annotate your test class like this:
#ExtendWith(MockitoExtension.class)
public class MyClassTest {
...
}
If you are stuck with JUnit4 then you should use another combination:
#RunWith(MockitoJUnitRunner.class)
public class MyClassTest {
...
}
Once you've done that the SERVICEBUILDER will be mocked but now you will have to configure the behaviour of that mock, like what is going to be returned by the SERVICEBUILDER methods. I can see 4 methods in total, namely remote, withConfig, withCall, and makeClient. You will have to do Mockito's when/thenReturn configurations.
MyClass.materialValue is null.
But even when your mock will be properly configured you will still encounter the original IllegalArgumentException: Material Name cannot be null. This is because MyClass.materialValue will still be null and looks like CredentialsProvider cannot accept that. As I can see, that field is supposed to be injected by Spring using the #Value annotation, but remember this class no longer contains anything from Spring. As in problem 1, you have to pass the value through the MyClass constructor.
Once all of these problems are solved you can introduce a thin Spring configuration like MyHostServiceClientConfiguration (or whatever name suits you) that would serve as a provider of necessary properties/dependencies for MyHostServiceClientFactory (existing MyClass) and then this factory can provide you with a MyHostServiceClient bean through a method like MyHostServiceClientConfiguration#myHostServiceClient annotated with #Bean.
Conceptually your MyHostServiceClientFactory will look like this:
public class MyHostServiceClientFactory {
private final String materialValue;
private final ServiceBuilder serviceBuilder;
public MyHostServiceClientFactory(String materialValue, ServiceBuilder serviceBuilder) {
this.materialValue = materialValue;
this.serviceBuilder = serviceBuilder;
}
public MyHostServiceClient myHostClient(String qualiferOne, boolean useService) {
if(useService) {
return serviceBuilder
.remote(MyHostServiceClient.class)
.withConfig(qualifierOne)
.withCall(new CallerAttach(Caller.retro(defaultStrategy())), // Error Line2 Here
new SigningVisitor(new CredentialsProvider(materialValue))),
call -> call.doSomeStuff(StuffObject.getStuffInstance()))
.makeClient();
}
// can also be injected as a dependency rather than being hardcoded
DefaultStrategy<Object> defaultStrategy() {
final int valueA = 1;
final int valueB = 2;
return new DoSomeThingsBuilder()
.retry(valueA)
.doSomethingElse(valueB)
.create();
}
}
I'm trying to write tests for the following class, where map fields are being injected through constructor injection with an argument list of dependencies. How can I mock the dependencies?
#Component
public class ComponentInputValidator {
private final Map<String, FaceInterface> faceMap;
private final Map<String, ArmsInterface> armsMap;
private final Map<String, MobilityInterface> mobilityMap;
private final Map<String, MaterialInterface> materialMap;
private final RobotComponentStock robotComponentStock;
public ComponentInputValidator(List<MaterialInterface> materialList,
List<FaceInterface> faceList,
List<ArmsInterface> armsList,
List<MobilityInterface> mobilityList,
RobotComponentStock robotComponentStock){
this.faceMap = faceList.stream().collect(Collectors.toMap(faceInterface -> faceInterface.getCode().name(), Function.identity()));
this.armsMap = armsList.stream().collect(Collectors.toMap(armsInterface -> armsInterface.getCode().name(), Function.identity()));
this.mobilityMap = mobilityList.stream().collect(Collectors.toMap(mobilityInterface -> mobilityInterface.getCode().name(), Function.identity()));
this.materialMap = materialList.stream().collect(Collectors.toMap(materialInterface -> materialInterface.getCode().name(), Function.identity()));
this.robotComponentStock = robotComponentStock;
}
public boolean validateStockAvailability(RobotComponent robotComponent){
String face = robotComponent.getFace();
String arms = robotComponent.getArms();
String mobility = robotComponent.getMobility();
String material = robotComponent.getMaterial();
Code faceCode = faceMap.get(face).getCode();
Code armsCode = armsMap.get(arms).getCode();
Code mobilityCode = mobilityMap.get(mobility).getCode();
Code materialCode = materialMap.get(material).getCode();
if (robotComponentStock.getQuantity(faceCode)<1 ...{
...
return false;
}
return true;
}
}
FaceInterface, ArmsInterface, MobilityInterface, MaterialInterface are interfaces that have different implementations.
What I tried:
#MockBean
private RobotComponentStock robotComponentStock;
#MockBean
private List<FaceInterface> faceInterfaceList;
#MockBean
private List<MobilityInterface> mobilityInterfaceList;
#MockBean
private List<ArmsInterface> armsInterfaceList;
#MockBean
private List<MaterialInterface> materialInterfaceList;
#InjectMocks
private ComponentInputValidator componentInputValidator;
Got an error:
org.mockito.exceptions.misusing.InjectMocksException:
Cannot instantiate #InjectMocks field named 'componentInputValidator' of type 'class com.demo.robot_factory.service.ComponentInputValidator'.
You haven't provided the instance at field declaration so I tried to construct the instance.
However the constructor or the initialization block threw an exception : null
for the line
faceList.stream().collect(Collectors.toMap(faceInterface -> faceInterface.getCode().name(), Function.identity()));
You are mixing two different concepts in your test.
#MockBean is a Spring annotation used in Integration Tests. It will create a mock and Spring's normal injection mechanism will inject it into your Bean.
#InjectMock on the other hand is an annotation from Mockito used in Unit Tests.
I can recommend this Blog Post on the Subject:
#Mock vs. #MockBean When Testing Spring Boot Applications
If you want to write a Unit Test I would suggest swapping all the #MockBean annotations with Mockitos #Mock.
// same for the other dependencies you want to mock
#Mock
private List<MaterialInterface> materialInterfaceList;
#InjectMocks
private ComponentInputValidator componentInputValidator;
This should fix the exception.
Looking at your code I would suggest a totally different approach for your test.
I don't understand why you want to mock the Lists in the first place. Can't you just instantiate the Lists and construct your ComponentInputValidator by hand? For example:
#Test
void test(){
List<MaterialInterface> materialList = List.of(...)
//initialize your other dependencies here
//pass all Lists into the constructor
var validator = new ComponentInputValidator(materialList,...)
//make your assertions on the validator
}
I am trying to mock this method with postForEntity call -
public AuthorizeClient(RestTemplateBuilder builder, Config config) {
this.grantedUrl = config.grantedUrl();
this.restTemplate = HttpClientHelper.getRestTemplate(builder, authorizationConfig);
}
private final RestTemplate restTemplate;
private String grantedUrl;
public List<Permission> getPermissions(
PermissionsRequest permissionsRequest) {
try {
var headers = new HttpHeaders();
var request = new HttpEntity<PermissionsRequest>(permissionsRequest, headers);
var permissions = restTemplate.postForEntity(grantedUrl, request, Permission[].class);
return Arrays.asList(permissions.getBody());
} catch (HttpClientErrorException err) {
logger.error(err);
throw err;
}
}
Here is my test case -
RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
Config config = new Config();
#InjectMocks
AuthorizeClient authorizeClient = new AuthorizeClient(restTemplateBuilder, config);
#Mock
private RestTemplate restTemplate;
PermissionsRequest permissionsRequest;
ResponseEntity<Permission[]> expGrantedPermissions;
#Test
public void testAuthorizationPermissions() {
when(restTemplate.postForEntity(anyString(), any(), eq(Permission[].class))).thenReturn(expGrantedPermissions);
var res = authorizeClient.getAllGrantedPermissions(permissionsRequest);
assertNotNull(res);
}
I'm getting this error. Looks like mock is not created properly ..
java.lang.IllegalArgumentException: URI is not absolute
At this line -
var res = authorizeClient.getPermissions(permissionsRequest);
My AuthorizeClient is constructed like above..
Please suggest what am I missing.
Thanks in advance
From your example code I don't see a relation between your restTemplate mock and the AuthorizeClient class.
The problem is that your restTemplate field in your object is final. In this case - even so the #InjectMocks generally works with the new constructor - injection of the mock does not happen.
You might want to add the full stacktrace, but I would assume that config.grantedUrl() does not return a valid url.
I assume grantedUrl is a String. In that case you define behaviour for the wrong method. There is only a 4-param version of postForEntity, so you'll need to define the mock for that using Mockito.<Object>any() for the varargs parameter.
To fix this: You might want to mock the RestTemplateBuilder and define behaviour for the methods that are used by HttpClientHelper.getRestTemplate.
You might also want to consider using PowerMockito to mock the static method directly.
Alternatively you could refactor your code to pass the RestTemplate directly to the constructor instead of passing the builder. At least in your example the builder does not seem to be used for anything else within the class, so I would consider it a removable dependency.
Also using Constructor injection is considered the way to go by many.
I assume PermissionsRequest andPermission` are your classes, so I can't really test this specific case, but basically this should work:
Note that I assume a changed constructor for AuthorizeClient that accepts both config and restTemplate. Instead of using the annotation I setup the mock manually, because you used a real Config object in your example. If mocking both is an option, you can still use the #InjectMocks annotation.
#RunWith(MockitoJUnitRunner.class)
public class RestTemplateTest {
Config config = new Config();
#Mock
private RestTemplate restTemplate;
PermissionsRequest permissionsRequest;
ResponseEntity<Permission[]> expGrantedPermissions;
#Test
public void testAuthorizationPermissions() {
// init permissionsRequest and expGrantedPermissions, if you haven't done that
AuthorizeClient authorizeClient = new AuthorizeClient(config, restTemplate);
Mockito.when(restTemplate.postForEntity(Mockito.anyString(), Mockito.any(), Mockito.eq(Permission[].class), Mockito.<Object>any())).thenReturn(expGrantedPermissions);
List<Permission> res = authorizeClient.getAllGrantedPermissions(permissionsRequest);
assertNotNull(res);
}
}
Ps.:
For a standalone example of a different method on restTemplate you can check my answer here. This at least can help you verify that you mock the correct method.
I have a simple test case with a spied List of Mocked Objects that I then Inject into the class being tested
#Spy List<ValidateRule> ruleServices = new ArrayList<>();
#Mock
private EvaluateGroupType evaluateGroupType;
#Mock
private ValidateServiceRule validateServiceRule;
#InjectMocks
private ValidateRulesService validateRulesService;
#Before
public void init() throws Exception {
initMocks(this);
}
However, in the ValidateRulesService class the list is being injected to the wrong list.
List<Integer> demonstrationList = new ArrayList<>();
#Autowired
private List<ValidateRule> ruleServices;
I have also tried to inject it as a using Constructor injection and here the results are that the values are being Injected twice
List<Integer> demonstrationList = new ArrayList<>();
final private List<ValidateRule> ruleServices;
#Autowired
public ValidateRulesService(List<ValidateRule> ruleServices) {
this.ruleServices = ruleServices;
}
I'm not expecting DemonstationList to have any values in either circumstance. As it doesn't have the same name or is of the same type as rulesService based on what I have read in the docs for #injectmocks.
Am i doing something wrong here, or is this a quirk of Mockito?
There are two things at play here. First of all, generics do not exist at runtime, so basically Mockito sees both List instances and then should pick one. The second issue is that your field is declared as final, which Mockito will skip when injecting mocks/spies.
With Mockito 1.x (this is the default when using Spring boot 1.x), you can't change this behaviour as far as I'm aware, so the only solution is to inject the fields by yourself in a #SetUp method:
private ValidateRulesService validateRulesService;
#Spy
private List<ValidateRule> ruleServices = new ArrayList<>();
#Before
public void setUp() {
// Inject the mocks/spies by yourself
validateRulesService = new ValidateRulesService(ruleServices);
}
This is also mentioned in Robert Mason's answer.
With Mockito 2.x on the other hand, it will prioritize fields using the same field name as the spy/mock, as you can see in the documentation:
Field injection; mocks will first be resolved by type (if a single type match injection will happen regardless of the name), then, if there is several property of the same type, by the match of the field name and the mock name.
And also:
Note 1: If you have fields with the same type (or same erasure), it's better to name all #Mock annotated fields with the matching fields, otherwise Mockito might get confused and injection won't happen.
However, be aware that if you use Mockito 2, it will ignore final fields when it injects the mocks/spies:
The same stands for setters or fields, they can be declared with private visibility, Mockito will see them through reflection. However fields that are static or final will be ignored.
(Emphasis is my own)
So, if you use Mockito 2.x, it should correctly inject the ruleServices if you remove the final keyword.
I managed to get this to correctly mock the correct list by altering the constructor injection implementation
The test
#Spy
List<ValidateRule> ruleServices = new ArrayList<>();
#Mock
private EvaluateGroupType evaluateGroupType;
#Mock
private ValidateServiceRule validateServiceRule;
private ValidateRulesService validateRulesService;
#Before
public void init() throws Exception {
initMocks(this);
ruleServices = Arrays.asList(evaluateGroupType, validateServiceRule);
validateRulesService = new ValidateRulesService(ruleServices);
}
The Class being tested
private List<ValidateRule> ruleServices;
private List<ResponseTo> responseToList = new ArrayList<>();
#Autowired
public ValidateRulesService(List<ValidateRule> ruleServices) {
this.ruleServices = ruleServices;
}