This question already has answers here:
How do I test a class that has private methods, fields or inner classes?
(58 answers)
Closed 4 years ago.
I have a class which is used for email validation. The class has two private String fields out of which i have injected the value of one from application.properties.
public class EmailValidation {
private final String someString = "xyz"
#Value("${regex}")
private String emailRegex;
// methods
}
public class EmailValidaitonTest {
private final EmailValidation obj = new EmailValidation();
//missing emailRegex
}
Now i have to write a unit test for this. This class has no dependency, so i just decided to use the new operator in EmailValidationTest class for the class object. Now, i can access the String someString but i cannot have the value of emailRegex since it was injected by Spring. How can i set its value in the test class and i want its value to be same as in my application.properties.
You use ReflectionTestUtils.setField to inject property value in test cases.
public class EmailValidationTest{
private #InjectsMock EmailValidation validation;
private String emailRegexPattern = "^\w+#[a-zA-Z_]+?\.[a-zA-Z]{2,3}$";
#BeforeAll
public void setUp(){
ReflectionTestUtils.setField(validation, "emailRegex", emailRegexPattern);
}
//your test cases over here.
}
It's preferable if you can write unit tests without loading up Spring context. You can have your class set up like this:
public class EmailValidation {
private final String regex;
#Autowired
EmailValidation (#Value("${regex:}") String regex) {
this.regex = regex;
}
}
Now in your test class, you can instantiate your emailValidation class through constructor param.
private final EmailValidation obj = new EmailValidation("myRegex");
As #JB Nizet points out, it's better to have a static final field for valid email regex or just call through a library.
Related
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 have created a validator component in spring boot and I am keeping a regex expression in the application.properties. I have used #Value annotation to get the value of regex in my component and I am compiling the Pattern outside any method or constructor and that is giving me null pointer exception as the regex is not getting it's value at that time. But when I move the pattern to some method, it's working fine. Why is that?
Why is #Value not working even though object is created using #Component
Look at the code below:
Code returning NullPointerException:
#Component
public class ValidString implements ConstraintValidator<ValidString, String> {
#Value("${user.input.regex}")
private String USER_INPUT_REGEX;
private Pattern USER_INPUT_PATTERN = Pattern.compile(USER_INPUT_REGEX);
#Override
public boolean validate(String userInput, ConstraintValidatorContext constraintValidatorContext) {
return USER_INPUT_PATTERN.matcher(userInput).find();
}
}
Code working fine:
#Component
public class ValidString implements ConstraintValidator<ValidString, String> {
#Value("${user.input.regex}")
private String USER_INPUT_REGEX;
private Pattern USER_INPUT_PATTERN;
#Override
public boolean validate(String userInput, ConstraintValidatorContext constraintValidatorContext) {
USER_INPUT_PATTERN = Pattern.compile(USER_INPUT_REGEX);
return USER_INPUT_PATTERN.matcher(userInput).find();
}
}
Also if you could explain why the first one is not working and second one is working, that'd be great.
application.properties
user.input.regex = ^[a-zA-Z0-9/\\-_ \\s+]*$
Field initializers (first example in question) are executed during the class constructor execution. #Value is injected by Spring after the constructor returns, using reflection. This means that you cannot have initializers using #Value-injected values.
The issue can be resolved by constructor or setter injection:
// Inject using constructor
#Component
public class ValidString implements ConstraintValidator<ValidString, String> {
private Pattern USER_INPUT_PATTERN;
#Autowired
public ValidString(#Value("${user.input.regex}") String regex) {
this.USER_INPUT_PATTERN = Pattern.compile(regex);
}
// Inject using setter method
#Component
public class ValidString implements ConstraintValidator<ValidString, String> {
private Pattern USER_INPUT_PATTERN;
#Autowired
private void setUserInputRegex(#Value("${user.input.regex}") String regex) {
this.USER_INPUT_PATTERN = Pattern.compile(regex);
}
Pattern USER_INPUT_PATTERN is before spring process.
the class ValidString object initial order is: ->process field initial->constructor -> inject field
so, when you use 1 code, it must be null point, because field has no inject
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 have Spring bean
#Component
public class CustomUriBuilder {
private final String cUrl;
private final String redirectUri;
private final String clientId;
public CustomUriBuilder (#Value("${project.url}") String cUrl,
#Value("${project.redirect.url}") String redirectUri, #Value("${project.client-id}") String clientId) {
this.cUrl= cUrl;
this.redirectUri = redirectUri;
this.clientId = clientId;
}
//No default constructor
}
I need to inject this bean in another bean
#Component
public class LinksBuilder {
#Autowired
private final CustomUriBuilder customUriBuilder ;
//No constructors
}
But i got compilation error
Error:(21, 34) java: variable customUriBuilder not initialized in the
default constructor
At the same time i am injecting LinksBuilder(as final) in another controller without any problems
#RestController
#RequestMapping(produces = "application/json")
public class resourceV4 {
private final LinksBuilder linksBuilder;
//No constructors
.
}
Why in the first case it gives an error but in the second case it is working fine?
Since spring 4.3 there is a feature - you can not add Autowired annotation for the final fields injection
You must have a constructor initialization for the final fields. I can not see one for the LinksBuilder and resourceV4. See lombok #RequiredArgsContructor for short.
It is impossible that one class with a final field is compiled ok and without constructor, but another one with an error.
Run other compilers or build ways (terminal gradlew/maven etc) to clarify the situation for you
I have a classes:
public class Sender {
private final SomeClass firstField;
private final SomeClass secondField;
private Sender(SomeClass firtsField, SomeClass secondField){
this.firstField = firstField;
this.secondField = secondField;
}
}
#RunWith(MockitoJUnitRunner.class)
public class SenderTest{
#Mock
private firstField;
#Mock
private secondField;
}
Everything are looking grade, but looks like it injects the same objects in two fields or something like this. When I am trying to use when(..).thenReturn() for one field it sets data two another and vise verse; And the most strange that it works fine in debug mode. What can you say?
Mockito has some problems with constructor injection of two or more fields of the same type. But it works perfectly if you use setter injection.
So you can refactor "Sender" class like this:
public class Sender {
private SomeClass firstField;
private SomeClass secondField;
public void setFirstField(SomeClass firstField) {
this.firstField = firstField;
}
public void setSecondField(SomeClass secondField) {
this.secondField= secondField;
}
}
Remember that if class has both the constructor and setters, Mockito will choose the constructor for injection and completely ignore setters.
Edit: if you definitely need to use constructor for some reason, you can always mock fields manually instead of using Mockito annotations.
So in your case Sender would stay the same and SenderTest would be like this:
public class SenderTest {
private SomeClass firstField;
private SomeClass secondField;
private Sender sender;
#Before
public void setUp() {
firstField = Mockito.mock(SomeClass.class);
secondField = Mockito.mock(SomeClass.class);
sender = new Sender(firstField, secondField);
}
#Test
public void smokeTest() {
}
}
It depends what SomeClass is itself. It it a data (POJO) object, it's worth to create them in test (and i.e. fill with random generated values).
If it is a service. It can be sign for a architecture problem. why do you need two copies of the same service? Probably it makes sense to do some refactoring.