JUnit Test Constructor Injection of List of Dependencies - java

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
}

Related

Java Unittest Mocked Unittest Not working

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();
}
}

How to mock an instance variable using Mockito in java?

I'm a noob to unit testing and use of mockito
I have a class
public class SystemTenancyConfig {
private String systemTenancy;
}
I have used this in another class where I'm getting the value:
#Inject
SystemTenancyConfig systemTenancyConfig;
String val = systemTenancyConfig.getsystemTenancy();
How do I mock systemTenancyConfig.getsystemTenancy() to be set to a string say "Test"?
UpdatE:
#Mock
private SystemTenancyConfig systemTenancyConfig;
when(systemTenancyConfig.getSystemTenancy()).thenReturn("test");
is giving me a NPE
the condition when getsystemTenancy will trigger your mock
when(systemTenancy.getsystemTenancy()).thenReturn(what you want it return);
systemTenancy.getsystemTenancy()
also #Mock over the Object you want to mock the whole Object
example
#Inject
private SystemTenancyConfig systemTenancyConfig;
#Test
function void testingSomething(){
when(systemTenancyConfig.getSystemTenancy()).thenReturn("test"); // condition to trigger the mock and return test
String val = systemTenancyConfig.getsystemTenancy();
}

I can get a Spring #Value at one of my test classes, but can't at other

Im testing with JUnit a project, and it will take some values from an application-test.properties.
I made a class called TestPropertiesProvider which is meant to get the values and send them to my test classes but is not working while at my test class i can get the values with no problem!
This is my test class. (Here my Value annotation is working and im getting my props).
#SpringBootTest
#TestPropertySource(locations = "classpath:application-test.properties")
#TestInstance(Lifecycle.PER_CLASS)
// The annotations SpringBootTest, and TestPropertySource, shouldn't be here, just put them to see if i could
// get the values here.
public class ODServiceImplTest {
#Value("${ondemand.server.port}")String si; // THIS IS WORKING, BUT I DONT WANNA USE THE #Value HERE!
ODService odService = new ODServiceImpl();
TestPropertiesProvider propsProvider = new TestPropertiesProvider();
#BeforeEach
void setUp() {
ReflectionTestUtils.setField(odService, "converterFactory", new ConverterFactory());
ReflectionTestUtils.setField(odService, "SERVER_NAME", propsProvider.getSERVER_NAME());
ReflectionTestUtils.setField(odService, "SERVER_PORT", propsProvider.getSERVER_PORT());
ReflectionTestUtils.setField(odService, "USER_ID", propsProvider.getUSER_ID());
ReflectionTestUtils.setField(odService, "PASSWORD", propsProvider.getPASSWORD());
}
#Test
void getDocumentDtoByValueTest() {
...
...
And this is my TestPropertiesProvider class, i want my values to be taken here, so with the getters i can use them here.
#SpringBootTest
#TestPropertySource(locations = "classpath:application-test.properties")
#Component
public class TestPropertiesProvider {
// Connection params.
#Value("${ondemand.server.port}")String si; // THIS IS NOT WORKING, I WANT THIS TO WORK.
private final String USER_ID = "DH01A";
private final String PASSWORD = "AR2SB";
private final String SERVER_NAME = "servername...";
private final String SERVER_PORT = "1415";
// Example values for OnDemand searchs.
private final String CORRECT_FOLDER_NAME = "DECF001";
private final String CORRECT_FOLDER_NAME_2 = "DH0001";
private final String NRO_ENVIO_SEARCH_VALUE = "20180500022E";
private final String NRO_ENVIO_SEARCH_FIELD = "NRO_ENVIO";
private final String PDF_FORMAT = "PDF";
// Utility methods and getters...
...
...
Hope anybody can help me, thanks! I'm using Spring Boot and JUnit 5.
UPDATE: The not working #Value 's are setting null at the variables.
My both java classes are at the same application, at 'src/test/java/' but at differents packages. My application have only one 'application-test.properties' at my 'src/test/resources/ folder.
Finally i could solve it, adding the annotation #TestInstance(Lifecycle.PER_CLASS).
This way:
#SpringBootTest
#TestPropertySource(locations = "classpath:application-test.properties")
#TestInstance(Lifecycle.PER_CLASS)
#Component
public class TestPropertiesProvider {
...
...
I really don't know how is this working, but solved me problem. I would really appreciate if somebody can explain it to me! Searching at Docs says that my instance will be just one of TestPropertiesProvider for all my tests, but that doesn't explain the problem I got.
In your ODServiceImplTest class your are creating an instance of TestPropertiesProvider class manually instead of using spring dependency injection mechanism.
So this will not inject the value of ondemand.server.port property in si variable. Instead you need to autowire TestPropertiesProvider in ODServiceImplTest as below.
#Autowired
TestPropertiesProvider propsProvider;

Mockito #InjectMocks not assigned a #Spy to the correct field

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;
}

How to do integ test using mockito

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.

Categories