Junit test class for dependency with value from application.properties - java

I am writing Junit test case for the following class :
#Component
public class ExpandParam {
/* expand parameter with value "expand" */
#Value("${api.expand.value}")
private String expandParam;
public MultiValueMap<String, String> getExpandQueryParam(String[] expand) {
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
// Creating comma separated format string
StringBuilder builder = new StringBuilder();
for (String value : expand) {
if(!expand[expand.length-1].equals(value)) {
builder.append(value+", ");
}
else {
builder.append(value);
}
}
String expandText = builder.toString();
queryParams.add(expandParam, expandText);
return queryParams;
}
}
The test class is following :
public class ExpandParamTest {
#InjectMocks
#Spy
ExpandParam expandQueryParam;
// #Value("${api.expand.value}")
// private String expandParam;
private String[] expand = {"fees"};
#Before
public void setup() {
ReflectionTestUtils.setField(expandQueryParam, "expandParam", "expand");
}
#Test
public void testExpandParam() {
MultiValueMap<String, String> queryParams = expandQueryParam.getExpandQueryParam(expand);
try {
System.out.println(new ObjectMapper().writeValueAsString(queryParams));
} catch (JsonProcessingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
In application. properties files I have set the values :
#expand param
api.expand.value: expand
I am new to this, can any one tell me where I am making the mistake:
Getting the following error:
java.lang.IllegalArgumentException: Either targetObject or targetClass for the field must be specified
at org.springframework.util.Assert.isTrue(Assert.java:121)
at org.springframework.test.util.ReflectionTestUtils.setField(ReflectionTestUtils.java:178)
at org.springframework.test.util.ReflectionTestUtils.setField(ReflectionTestUtils.java:107)
at org.springframework.test.util.ReflectionTestUtils.setField(ReflectionTestUtils.java:91)
at com.aig.rs.products.orchestrator.api.utils.ExpandParamTest.setup(ExpandParamTest.java:29)

#Value is a spring annotation, it depends on the Spring Context to function. If you want #Value to read the value from your application properties then you need to convert your unit test into a #SpringBootTest. Take a look at this tutorial to understand a bit more about Spring Test.
You're also using ReflectionTestUtils.setField(expandQueryParam, "expandParam", "expand"); which will just set a value to this field, not read it from properties. This exception you're seeing is because expandQueryParam is null, these annotations #Spy and #InjectMocks are Mockito annotations and for them to initialize your object you need to enable mockito annotations, you can do this by adding #ExtendWith(MockitoExtension.class) on top of your class or using MockitoAnnotations.initMocks(this) in setUp method.
I don't think you need mockito to test this class, in my opinion going for a Spring Test would be a better option this way you can also test the reading of the property key.

Related

Mockito failing inside internal call for mocked method

I'm trying to mock the return value for a method using the when call from mockito. However, I'm new to this and I may perhaps be misunderstanding how mockito works, since the call is failing inside the method mocked when that calls another method. I thought regardless of how that method is implemented, I should be getting the return value I'm asking for? Or do I need to mock also the internals for that method? I feel that shouldn't be it.
public boolean verifyState(HttpServletRequest request, String s) {
String stateToken = getCookieByName(request, STATE_TOKEN);
String authToken = getCookieByName(request, AUTHN);
boolean isValidState = true;
if (isValidState) {
try {
log.info(getEdUserId(stateToken, authToken));
return true;
} catch (Exception e) {
ExceptionLogger.logDetailedError("CookieSessionUtils.verifyState", e);
return false;
}
} else {
return false;
}
}
public String getEdUserId(String stateToken, String authToken) throws Exception {
String edUserId;
Map<String, Object> jwtClaims;
jwtClaims = StateUtils.checkJWT(stateToken, this.stateSharedSecret); // Failing here not generating a proper jwt token
log.info("State Claims: " + jwtClaims);
edUserId = sifAuthorizationService.getEdUserIdFromAuthJWT(authToken);
return edUserId;
}
My test:
#ActiveProfiles(resolver = MyActiveProfileResolver.class)
#WebMvcTest(value = CookieSessionUtils.class, includeFilters = {
#ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {ApiOriginFilter.class, ValidationFilter.class})})
class CookieSessionUtilsTest {
#Autowired
private CookieSessionUtils cookieSessionUtils; // Service class
#Mock
private CookieSessionUtils cookieSessionUtilsMocked; // Both the method under test and the one mocked are under the same class, so trying these two annotations together.
#Mock
private HttpServletRequest request;
#BeforeEach
public void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test
public void testVerifyState1() throws Exception {
//...Some mocks for getCookieName
UUID uuid = UUID.randomUUID();
when(cookieSessionUtils.getEdUserId(anyString(), anyString()).thenReturn(eq(String.valueOf(uuid))); // When this line runs it fails on verifyState method
assertTrue(cookieSessionUtils.verifyState(request, ""));
}
UPDATE
Attempt using anyString() instead of eq().
Thank you.
Your test is broken in a few places.
Setting expectations on a real object
You should call Mockito.when on mocks and spies, not on System under test. Mockito normally reports it with a clear error message, but you throw a NPE from getEdUserId, so this is reported instead. The NPE stems from the fact that both eq and anyString return null, which is passed to the real method.
Invalid use of matchers
As #StefanD explained in his answer eq("anyString()") is not matching any string. It matches only one string "anyString()"
Returning a mather instead of real object
thenReturn(eq(String.valueOf(uuid)))
This is illegal position for a matcher.
Mixing Mockito and Spring annotations in a WebMvcTest
This is a common error. Mockito does not inject beans to the spring context.
From the code provided it is unclear what CookieSessionUtils is (Controller? ControllerAdvice?) and what is the correct way to test it.
Update
It seems that you are trying to replace some methods under test. A way to do it is to use a Spy.
See https://towardsdatascience.com/mocking-a-method-in-the-same-test-class-using-mockito-b8f997916109
The test written in this style:
#ExtendWith(MockitoExtension.class)
class CookieSessionUtilsTest {
#Mock
private HttpServletRequest request;
#Mock
private SifAuthorizationService sifAuthorizationService;
#Spy
#InjectMocks
private CookieSessionUtils cookieSessionUtils;
#Test
public void testVerifyState1() throws Exception {
Cookie cookie1 = new Cookie("stateToken", "stateToken");
Cookie cookie2 = new Cookie("Authn", "Authn");
when(request.getCookies()).thenReturn(new Cookie[]{cookie1, cookie2});
UUID uuid = UUID.randomUUID();
doReturn(String.valueOf(uuid)).when(cookieSessionUtils).getEdUserId(anyString(), anyString());
assertTrue(cookieSessionUtils.verifyState(request, ""));
}
}
An alternative way is to call the real method, but to mock all collaborators: StateUtils and sifAuthorizationService. I would probably go with this one, if you want to test public getEdUserId.
Test written when mocking collaborators:
#ExtendWith(MockitoExtension.class)
class CookieSessionUtilsTest {
#Mock
private HttpServletRequest request;
#Mock
private SifAuthorizationService sifAuthorizationService;
#InjectMocks
private CookieSessionUtils cookieSessionUtils;
#Test
public void testVerifyState1() throws Exception {
Cookie cookie1 = new Cookie("stateToken", "stateToken");
Cookie cookie2 = new Cookie("Authn", "Authn");
when(request.getCookies()).thenReturn(new Cookie[]{cookie1, cookie2});
UUID uuid = UUID.randomUUID();
when(sifAuthorizationService.getEdUserIdFromAuthJWT(cookie2.getValue())).thenReturn(String.valueOf(uuid));
assertTrue(cookieSessionUtils.verifyState(request, ""));
}
}
I took the assumption that StateUtils.checkJWT does not need to be mocked
The points above are still valid and need to be resolved in either case.
Remarks
As the system under test is currently a Service, I suggest to drop WebMvcTest and test it with plain mockito instead.
Should SUT be a service? It is more typical to handle auth code in filters.
note usage of doReturn when stubbing a method on a spy.
You use mocks in more places than needed. For example Cookie is trivial to construct, there is no point in using a mock
The error is here:
when(cookieSessionUtils.getEdUserId(eq("anyString()"), eq("anyString()"))).thenReturn(eq(String.valueOf(uuid)));
It should read like
when(cookieSessionUtils.getEdUserId(anyString()), anyString()).thenReturn(uuid);
Please refer to the Mockito documentation of Argument matchers.
Because the argument matchers looking for the string "anyString()" they never match the actual parameters the method call is providing and so there is never returned the uuid you expecting.

Creating an annotation for JUnit 4/5 to initialize and inject an object in tests

I am developing a testing library for Kafka, Kafkaesque. The library lets you develop integration tests for Kafka using a fluid and elegant (?!) API. For now, I develop the version for Spring Kafka.
The library needs to be initialized in every test:
#Test
void consumeShouldConsumeMessagesProducesFromOutsideProducer() {
kafkaTemplate.sendDefault(1, "data1");
kafkaTemplate.sendDefault(2, "data2");
new SpringKafkaesque(broker)
.<Integer, String>consume()
.fromTopic(CONSUMER_TEST_TOPIC)
.waitingAtMost(1L, TimeUnit.SECONDS)
.waitingEmptyPolls(5, 100L, TimeUnit.MILLISECONDS)
.withDeserializers(new IntegerDeserializer(), new StringDeserializer())
.expecting()
.havingRecordsSize(2)
.assertingThatPayloads(Matchers.containsInAnyOrder("data1", "data2"))
.andCloseConsumer();
}
Instead of manually initializing the SpringKafkaesque object, I want to create an annotation that does the magic for me. Something like the #EmbeddedKafka annotation of Spring Kafka.
#SpringBootTest(classes = {TestConfiguration.class})
#Kafkaesque(
topics = {SpringKafkaesqueTest.CONSUMER_TEST_TOPIC, SpringKafkaesqueTest.PRODUCER_TEST_TOPIC})
class SpringKafkaesqueTest {
#Autowired
private Kafkaesque kafkaesque;
#Test
void consumeShouldConsumeMessagesProducesFromOutsideProducer() {
kafkaTemplate.sendDefault(1, "data1");
kafkaTemplate.sendDefault(2, "data2");
kafkaesque
.<Integer, String>consume()
.fromTopic(CONSUMER_TEST_TOPIC)
.waitingAtMost(1L, TimeUnit.SECONDS)
.waitingEmptyPolls(5, 100L, TimeUnit.MILLISECONDS)
.withDeserializers(new IntegerDeserializer(), new StringDeserializer())
.expecting()
.havingRecordsSize(2)
.assertingThatPayloads(Matchers.containsInAnyOrder("data1", "data2"))
.andCloseConsumer();
}
Is it possible? Any suggestion?
JUnit 4
One possible solution is to create a custom annotation processing using reflection. You can get the test method name with #Rule, so for example:
public class CustomAnnotationTest {
private SpringKafkaesque kafkaesqueInstance;
#Rule
public TestName testName = new TestName();
#Before
public void init() {
Method method = null;
try {
method = this.getClass().getMethod(testName.getMethodName());
} catch (Exception ex) {
// handle exceptions
}
if (method.isAnnotationPresent(EmbeddedKafka.class)) {
// Init your SpringKafkaesque instance here
// kafkaesqueInstance = new SpringKafkaesque(broker)
//
}
}
#EmbeddedKafka
#Test
public void testCustomAnnotated() {
// your test here
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
#interface EmbeddedKafka {
}
}
You need to store this instance in the class-level variable. For the methods with no #EmbeddedKafka annotation, this variable will be null.
JUnit 5
With JUnit 5 you may consider using parameter injection with ParameterResolver. First of all, you need to implement this interface:
public class KafkaesqueResolver implements ParameterResolver {
#Override
public boolean supportsParameter(ParameterContext parameterContext,
ExtensionContext extensionContext) throws ParameterResolutionException {
return parameterContext.getParameter().getType() == SpringKafkaesque.class;
}
#Override
public Object resolveParameter(ParameterContext parameterContext,
ExtensionContext extensionContext) throws ParameterResolutionException {
// Create an instance of SpringKafkaesque here and return it
return new SpringKafkaesque();
}
}
Next, add #ExtendWith(KafkaesqueResolver.class) annotation to your test class, and add a parameter to your test method, where you need the instance of SpringKafkaesque:
#ExtendWith(KafkaesqueResolver.class)
public class ParamInjectionTest {
#Test
public void testNoParams() {
// nothing to inject
}
#Test
public void testWithParam(SpringKafkaesque instance) {
// do what you need with your instance
}
}
No custom annotation required in this case.

Mocking a ListState in Apache Flink 1.4

I am writing some test code for a processElement function in Apache Flink 1.4:
public class ProcessFunctionClass {
public void processElement(Tuple2<String, String> tuple2, Context context, Collector<Tuple2<String, String>> collector) {
// if the state is empty, start a timer
if (listState.get().iterator().hasNext() == false)
context.timerService().registerEventTimeTimer(1000);
listState.add("someStringToBeStored");
// ...
}
}
public class ProcessFunctionClassTest {
private ProcessFunctionClass processFunctionClass;
#Mock
private ListState<String> listState;
#Before
public void setUp() throws Exception {
processFunctionClass = new ProcessFunctionClass();
}
#Test
public void testProcessElement() {
ListState mockListState = mock(ListState.class);
Iterable mockIterable = mock(Iterable.class);
Iterator mockIterator = mock(Iterator.class);
MockitoAnnotations.initMocks(this);
when(tDPListState.get()).thenReturn(mockIterable);
when(tDPListState.get().iterator()).thenReturn(mockIterator);
when(tDPListState.get().iterator().hasNext()).thenReturn(false);
processFunctionClass.processElement(tuple2, context, collector);
// verify(...)
}
}
When I debug using my IDE, just before I step into the processElement() method, listState is not null and appears to have been mocked successfully, but as soon as I get to listState.get().iterator().hasNext(), listState is null and I get a NullPointerException. What am I doing wrong here?
In ProcessFunctionClass you have a private listState variable.
In your test you create a completely unrelated mockListState variable and set some expectations on it.
For your test to work, you must provide a way (constructor or setter) to set ProcessFunctionClass.listState to desired value (your mocked list state)
On top of that, MockitoAnnotations.initMocks(this); seems to do nothing in your example: you haven't shown us any fields annotated with #Mock or #InjectMocks
Update
You are misusing #Mock annotation.
You should place it in the test class, not in class under test.
When placed in the test class, after a call to initMocks, the filed will be initialized with a mock of an appropriate type.
What you should fo instead:
remove MockitoAnnotations.initMocks(this);, you are creating all the mocks manually.
add a constructor in ProcessFunctionClass
public ProcessFunctionClass(ListState<String> listState) {
this.listState = listState
}
use this constructor in your test
var mockListState = mock(ListState.class);
var processFunctionClass = new ProcessFunctionClass();

Spring Boot - Assign value to Class Member from YML file - Null Pointer Exception

I am using below annotations in my config class to get the values from properties file(yml).
Configuration
EnableConfigurationProperties
ConfigurationProperties (prefix = "notification")
I am able to get the values inside public methods without problem using the class . But I am getting 'Error Creating bean' Error when I try to assign value instance variable of the class using config class.
Below is my code. Can someone please throw some light.
This is my config class
#Configuration
#EnableConfigurationProperties
#ConfigurationProperties (prefix = "notification")
public class NotifyYaml {
private String subscriptionId;
public String getSubscriptionId() {
return subscriptionId;
}
public void setSubscriptionId(String subscriptionId) {
this.subscriptionId = subscriptionId;
}
Below is the class where I am getting error during startup.
#Component
public class PubSubController {
#Autowired
private NotifyYaml notify;
public PubSubController() {
// TODO Auto-generated constructor stub
}
String projectId = "ccc-g-pre-proj-cacdate";
//Error in this line
String subscriptionId = notify.getSubscriptionId();
The #Autowired object only gets filled in after the object is created.
This means that while the object is being created, it tries to call a method from a null object.
I would suggest using something like a #PostConstruct method. (Note: you will need to include javax.annotations into your project somehow.)
String subscriptions; // remove the value for now...
#PostConstruct
private void init() {
subscriptions = notify.getSubscriptionId(); // ...and add it back in here.
}

How to obtain a field from my test in a Junit 5 jupiter extension

I have a Custom tag for my tests for a driver ive created. Im looking for a way to initialize and quit this driver during the BeforeEach and the AfterEach, using the new Junit5 jupiter extensions.
#Target({TYPE, FIELD, ANNOTATION_TYPE })
#Retention(RUNTIME)
#ExtendWith(MyExtension.class)
public #interface MyDriver
{
}
I've seen there is a AnnotationSupport.class that should help you to obtain the fields annotated with certain annotation but havent found any example.
What I want is just to be able to work with the field annotated with my annotation from the extension.
You could go about it like this:
public class MyExtension implements BeforeEachCallback {
#Override
public void beforeEach(ExtensionContext context) {
context.getTestInstance().ifPresent(testInstance -> {
List<Field> driverFields = AnnotationSupport.findAnnotatedFields(testInstance.getClass(), MyDriver.class);
for (Field driverField : driverFields) {
try {
Object fieldValue = driverField.get(testInstance);
// Do whatever you want with the field or its value
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
});
}
}
Which would then be called before each test in a test class like this:
#MyDriver
class SomeTestThatUsesDriver {
#MyDriver
Object fieldWithAnnotation = "whatever";
#Test
void aTest() {
...
}
}
What I wouldn't do, though, is to use the annotation #MyDriver for both adding the extension and marking a field. I'd rather got with an additional annotation like #MyDriverField or add the extension directly at the test class.

Categories