Divyanka Kumari
Divyanka Kumari

Reputation: 1

How to write JUnit Tests using Mockito on JSON transformation?

I have a service class which transforms JSON from one pattern to other using DynamoDB. This class have various methods to manipulate JSON fields, as shown below. I have to write JUnit tests on this code using Mockito. The convertToProviderJson method transform one JSON which is coming from 3rd party, to a predefined template and following methods are manipulating details present on the transformed JSON.

I am new to JUnit & Mockito, how shall I proceed?

```
@Service
public class ServiceClass {
    
    public String convertToProviderJson(String consumerString, String providerTemplateJson)
            throws JsonProcessingException {
        //create ObjectMapper instance
        ObjectMapper objectMapper = new ObjectMapper();

        //convert json file to map
        String currentFieldName = "";
        String currentTemplateKey = "";
        boolean templateMatchError = false;
        Map<?, ?> providerMap;
        Map<String, Object> providerOutputMap = new LinkedHashMap<>();
        System.out.println("Provider JSON");
        if(!UtilityClass.isJSONValid(consumerString)) {
            throw new MalformedJsonException("Incorrect Consumer Input JSON.");
        }

        if(!UtilityClass.isJSONValid(providerTemplateJson)) {
            throw new MalformedJsonException("Incorrect Provider Template JSON.");
        }
        try {
            JSONObject consumerJson = new JSONObject(consumerString);
            providerMap = objectMapper.readValue(providerTemplateJson, Map.class);

            //iterate over Provider Template map.
            for (Map.Entry<?, ?> entry : providerMap.entrySet()) {
                String key = (String) entry.getKey();
                currentTemplateKey = key;
                String value = (String) entry.getValue();
                Pattern p = Pattern.compile(TransformationConstants.TEMPLATE_FUNCTION_REGEX);

                Matcher matcher = p.matcher((CharSequence) entry.getValue());
                if (matcher.matches()) {
                    String[] splitString = value.split(LEFT_ROUND_BRACKET);
                    String functionName = splitString[0];
                    String fieldName = splitString[1].split(RIGHT_ROUND_BRACKET)[0];
                    currentFieldName = fieldName;
                    Object fieldValue = invokeFunction(consumerJson, functionName, fieldName);
                    providerOutputMap.put(key, fieldValue);
                } else {
                    templateMatchError = true;
                    break;
                }
            }

        } catch(JsonEOFException e) {
            throw new MalformedJsonException("Incorrect Provider Template JSON.");
        } catch (Exception e) {
            throw new MalformedJsonException("Field '" + currentFieldName + "' missing in input json.");
        }
        if(templateMatchError) {
            throw new MalformedJsonException("Value for Field '" + currentTemplateKey
                    + "' in template JSON is not in correct format.");
        }
        String outputJson = objectMapper.writeValueAsString(providerOutputMap);
        System.out.println("Provider JSON: " + outputJson);
        return outputJson;
    }

    private Object invokeFunction(JSONObject consumerJson, String functionName, String fieldName)
            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        TransformationService obj = new TransformationService();
        Method method;
        method = obj.getClass().getMethod(functionName, JSONObject.class, String.class);
        return method.invoke(obj, consumerJson, fieldName);
    }

    public Object getField(JSONObject jsonObject, String fieldName) throws JSONException {
        if(jsonObject.has(fieldName)) {
            return jsonObject.get(fieldName);
        }
        throw new MalformedJsonException("Field '" + fieldName + "' missing in input json.");
    }
}

I have tried to write Unit test on getField() method after going through some articles. Here's my code, I know its wrong, how shall I proceed?

@Test   
public void hasFieldTest() {
    JSONObject obj = new JSONObject();
    obj.put("id", "1");
    obj.put("name", "divyanka");
    when(((Object) transformationMock.getField(jsonObjmock, "name")).thenReturn(objectMock);
    JSONAssert.assertEquals("{id:1}", obj, 'strict':false);     
}

Upvotes: 0

Views: 9156

Answers (1)

xerx593
xerx593

Reputation: 13289

To test (the getField method of) ServiceClass, i would approach like:

import static org.junit.jupiter.api.Assertions.assertThrows;
// ...

class ServiceClassTest {

   //Object/Class under test:
   private ServiceClass testee = new ServiceClass(); // create new or "obtain somewhere" (like @Autowired in Spring testing...)

  //... test all methods, lines:

  @Test
  public void testGetFieldOK() {
      // prepare "good" object:
      JSONObject obj = new JSONObject();
      obj.put("foo", "bar");

      // TEST/invoke (!):
      Object result = testee.getField(obj, "foo");
     
     // assert (with your assertion framework/style):
     // result is not null AND result == "bar"  
     // done!
  }

  @Test
  public void testGetFieldException() {
      // prepare "bad" object:
      JSONObject obj = new JSONObject();

      // Test/Expect exception -> https://stackoverflow.com/a/40268447/592355 ->:
      MalformedJsonException thrown = assertThrows(
       MalformedJsonException.class,
       () -> testee.getField(obj, "foo"),
       "Expected getField(obj, \"foo\") to throw, but it didn't"
      );

      //assert on exception (message):
      assertTrue(thrown.getMessage().contains("Field 'foo' missing in input json.")); 

  }

  //create more tests like that... (-> coverage),
  //.. WHEN real parameters, associated objects and class (invocations) are not applicable, mock them!
}

Thx to: https://stackoverflow.com/a/40268447/592355

And to summarize the main topics:

  • test a (as) real (as possible) object.
  • (try to) achieve coverage.
  • prefer "real implementations" to mocks, and use them only when real implementation is not applicable/too costly. (interfaces, external code/systems, ... internal code/systems, which "wiring" is too costly/not applicable and is covered by other tests.)

So in your code: ObjectMapper and TransformationService look like possible mocking candidates, but it is not worth, since they are created locally (in the methods to test).

UtilityClass could also be (power) mocked, but is it worth!?? :-)

In case UtilityClass would be an (external) productive (chargeable) API, you might want to mock it.

Upvotes: 1

Related Questions