From 68e7b54c908fe018c99ed08b6795e5827d2bc486 Mon Sep 17 00:00:00 2001 From: John Blum Date: Wed, 6 May 2020 00:43:03 -0700 Subject: [PATCH] Refactor importInto(:Region) to parse and convert JSON containing multiple objects into an array of PdxInstances each stored in the Region. Edit Javadoc. Resolves gh-67. --- .../json/JsonCacheDataImporterExporter.java | 95 ++++++++---- ...sonCacheDataImporterExporterUnitTests.java | 143 +++++++++++------- 2 files changed, 155 insertions(+), 83 deletions(-) diff --git a/spring-geode/src/main/java/org/springframework/geode/data/json/JsonCacheDataImporterExporter.java b/spring-geode/src/main/java/org/springframework/geode/data/json/JsonCacheDataImporterExporter.java index 940b2535..a9d7ddd2 100644 --- a/spring-geode/src/main/java/org/springframework/geode/data/json/JsonCacheDataImporterExporter.java +++ b/spring-geode/src/main/java/org/springframework/geode/data/json/JsonCacheDataImporterExporter.java @@ -23,6 +23,7 @@ import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.Writer; +import java.util.Arrays; import java.util.Optional; import org.apache.geode.cache.Region; @@ -36,10 +37,12 @@ import org.springframework.data.gemfire.util.CollectionUtils; import org.springframework.geode.data.AbstractCacheDataImporterExporter; import org.springframework.geode.data.CacheDataExporter; import org.springframework.geode.data.CacheDataImporter; +import org.springframework.geode.data.json.converter.JsonToPdxArrayConverter; import org.springframework.geode.data.json.converter.JsonToPdxConverter; import org.springframework.geode.data.json.converter.ObjectToJsonConverter; -import org.springframework.geode.data.json.support.JSONFormatterJsonToPdxConverter; -import org.springframework.geode.data.json.support.JSONFormatterPdxToJsonConverter; +import org.springframework.geode.data.json.converter.support.JSONFormatterJsonToPdxConverter; +import org.springframework.geode.data.json.converter.support.JSONFormatterPdxToJsonConverter; +import org.springframework.geode.data.json.converter.support.JacksonJsonToPdxConverter; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; @@ -59,6 +62,7 @@ import org.springframework.util.StringUtils; * @see org.springframework.geode.data.AbstractCacheDataImporterExporter * @see org.springframework.geode.data.CacheDataExporter * @see org.springframework.geode.data.CacheDataImporter + * @see org.springframework.geode.data.json.converter.JsonToPdxArrayConverter * @see org.springframework.geode.data.json.converter.JsonToPdxConverter * @see org.springframework.geode.data.json.converter.ObjectToJsonConverter * @see org.springframework.stereotype.Component @@ -68,6 +72,8 @@ import org.springframework.util.StringUtils; @SuppressWarnings("rawtypes") public class JsonCacheDataImporterExporter extends AbstractCacheDataImporterExporter { + private static final boolean APPEND_TO_FILE = false; + private static final int CONTENT_PREVIEW_LENGTH = 50; private static final int DEFAULT_BUFFER_SIZE = 32768; @@ -78,6 +84,8 @@ public class JsonCacheDataImporterExporter extends AbstractCacheDataImporterExpo private JsonToPdxConverter toPdxConverter = newJsonToPdxConverter(); + private JsonToPdxArrayConverter toPdxArrayConverter = newJsonToPdxArrayConverter(); + private ObjectToJsonConverter toJsonConverter = newObjectToJsonConverter(); // TODO configure via an SPI @@ -85,6 +93,11 @@ public class JsonCacheDataImporterExporter extends AbstractCacheDataImporterExpo return new JSONFormatterJsonToPdxConverter(); } + // TODO configure via an SPI + private @NonNull JsonToPdxArrayConverter newJsonToPdxArrayConverter() { + return new JacksonJsonToPdxConverter(); + } + // TODO configure via an SPI private @NonNull ObjectToJsonConverter newObjectToJsonConverter() { return new JSONFormatterPdxToJsonConverter(); @@ -100,6 +113,16 @@ public class JsonCacheDataImporterExporter extends AbstractCacheDataImporterExpo return this.toPdxConverter; } + /** + * Returns a reference to the configured {@link JsonToPdxArrayConverter}. + * + * @return a reference to the configured {@link JsonToPdxArrayConverter}. + * @see org.springframework.geode.data.json.converter.JsonToPdxArrayConverter + */ + protected @NonNull JsonToPdxArrayConverter getJsonToPdxArrayConverter() { + return this.toPdxArrayConverter; + } + /** * Gets a reference to the configured {@link ObjectToJsonConverter}. * @@ -129,7 +152,7 @@ public class JsonCacheDataImporterExporter extends AbstractCacheDataImporterExpo } /** - * Saves the given {@link String content} (e.g. {@literal JSON}) to the specified {@link Resource}. + * Saves the given {@link String content} (e.g. JSON) to the specified {@link Resource}. * * @param content {@link String} containing the content to save. * @param resource {@link Resource} to save the {@link String content} to; must not be {@literal null}. @@ -156,7 +179,7 @@ public class JsonCacheDataImporterExporter extends AbstractCacheDataImporterExpo } @NonNull Writer newWriter(@NonNull Resource resource) throws IOException { - return new BufferedWriter(new FileWriter(resource.getFile(), false)); + return new BufferedWriter(new FileWriter(resource.getFile(), APPEND_TO_FILE)); } @Nullable String formatContentForPreview(@Nullable String content) { @@ -182,14 +205,16 @@ public class JsonCacheDataImporterExporter extends AbstractCacheDataImporterExpo getResource(region, CLASSPATH_RESOURCE_PREFIX) .filter(Resource::exists) .map(this::getContent) - .map(this::toPdx) - .ifPresent(pdxInstance -> region.put(getIdentifier(pdxInstance), pdxInstance)); + .map(this::toPdxArray) + .map(Arrays::stream) + .ifPresent(pdxInstances -> pdxInstances.forEach(pdxInstance -> + region.put(getIdentifier(pdxInstance), pdxInstance))); return region; } /** - * Gets the content of the given {@link Resource} as an array of {@literal Byte#TYPE bytes}. + * Gets the contents of the given {@link Resource} as an array of {@literal Byte#TYPE bytes}. * * @param resource {@link Resource} from which to load the content; must not be {@literal null}. * @return an array of {@link Byte#TYPE bytes} containing the contents of the given {@link Resource}. @@ -230,8 +255,7 @@ public class JsonCacheDataImporterExporter extends AbstractCacheDataImporterExpo * @param pdxInstance {@link PdxInstance} to evaluate; must not be {@literal null}. * @return the {@link Object identifier} for the given {@link PdxInstance}. * @throws IllegalArgumentException if {@link PdxInstance} is {@literal null}. - * @throws IllegalStateException if the {@link Object identifier} for the {@link PdxInstance} - * cannot be determined. + * @throws IllegalStateException if the {@link Object identifier} for the {@link PdxInstance} cannot be determined. * @see org.apache.geode.pdx.PdxInstance * @see #getIdField(PdxInstance) */ @@ -250,13 +274,12 @@ public class JsonCacheDataImporterExporter extends AbstractCacheDataImporterExpo } /** - * Searches for a PDX {@link String field name} on the {@link PdxInstance} named {@literal id} and returns - * its value as the {@link Object identifier} for the {@link PdxInstance}. + * Searches for a PDX {@link String field name} called {@literal id} on the given {@link PdxInstance} + * and returns its value as the {@link Object identifier} for the {@link PdxInstance}. * * @param pdxInstance {@link PdxInstance} to evaluate; must not be {@literal null}. - * @return the value of the {@literal id} {@link String field name} of the {@link PdxInstance} if present. - * @throws IllegalStateException if the {@link PdxInstance} does not have an {@literal id} - * {@link String field name}. + * @return the {@link Object value} of the {@literal id} {@link String field} on the {@link PdxInstance}. + * @throws IllegalStateException if the {@link PdxInstance} does not have an id. * @see org.apache.geode.pdx.PdxInstance * @see #getIdentifier(PdxInstance) */ @@ -272,12 +295,12 @@ public class JsonCacheDataImporterExporter extends AbstractCacheDataImporterExpo } /** - * Returns a computed {@literal JSON} {@link Resource} containing {@literal JSON} data that will be loaded into - * the given a {@link Region}. + * Returns a computed JSON {@link Resource} containing JSON data to be loaded into the given {@link Region}. * - * @param region target {@link Region} used to compute the {@literal JSON} {@link Resource} to load. - * @return an {@link Optional} {@literal JSON} {@link Resource} containing {@literal JSON} data - * to load into the given {@link Region}. + * @param region target {@link Region} used to compute the JSON {@link Resource} to load. + * @param resourcePrefix {@link String} containing a path prefix (e.g. {@literal classpath:} used to indicate + * the location of the {@link Resource} to load. + * @return an {@link Optional} JSON {@link Resource} containing JSON data to load into the given {@link Region}. * @see org.springframework.core.io.Resource * @see org.apache.geode.cache.Region * @see java.util.Optional @@ -300,20 +323,19 @@ public class JsonCacheDataImporterExporter extends AbstractCacheDataImporterExpo } /** - * Returns the {@link String file system path} specifying the location for where to export the {@link Resource}. + * Returns a {@link String file system path} specifying the location for where to export the {@link Resource}. * - * @return the {@link String file system path} specifying the location for where to export the {@link Resource}. + * @return a {@link String file system path} specifying the location for where to export the {@link Resource}. */ protected @NonNull String getResourceLocation() { return String.format("%1$s%2$s", FILESYSTEM_RESOURCE_PREFIX, System.getProperty("user.dir")); } /** - * Convert {@link Object values} contained in the {@link Region} to {@literal JSON}. + * Convert {@link Object values} contained in the {@link Region} to JSON. * * @param region {@link Region} to process; must not be {@literal null}. - * @return a {@literal JSON} {@link String} containing the {@link Object values} - * from the given {@link Region}. + * @return a JSON {@link String} containing the {@link Object values} from the given {@link Region}. * @see org.apache.geode.cache.Region * @see #toJson(Object) */ @@ -338,10 +360,10 @@ public class JsonCacheDataImporterExporter extends AbstractCacheDataImporterExpo } /** - * Converts the given {@link Object} into a {@literal JSON} {@link String}. + * Converts the given {@link Object} into JSON. * - * @param source {@link Object} to convert into {@literal JSON}. - * @return a {@link String} containing the {@literal JSON} generated from the given {@link Object}. + * @param source {@link Object} to convert into JSON. + * @return a {@link String} containing the JSON generated from the given {@link Object}. * @see #getObjectToJsonConverter() */ protected @NonNull String toJson(@NonNull Object source) { @@ -349,14 +371,27 @@ public class JsonCacheDataImporterExporter extends AbstractCacheDataImporterExpo } /** - * Converts the array of {@literal JSON} {@link Byte#TYPE bytes} into a {@link PdxInstance}. + * Converts the given JSON object into a {@link PdxInstance}. * - * @param json array of {@link Byte#TYPE} containing {@literal JSON} data. - * @return a {@link PdxInstance} converted from the {@literal JSON} {@link Byte#TYPE bytes}. + * @param json array of {@link Byte#TYPE bytes} containing JSON. + * @return a {@link PdxInstance} converted from the JSON object. * @see org.apache.geode.pdx.PdxInstance * @see #getJsonToPdxConverter() */ protected @NonNull PdxInstance toPdx(@NonNull byte[] json) { return getJsonToPdxConverter().convert(json); } + + /** + * Converts the array of {@link Byte#TYPE bytes} containing multiple JSON objects + * into an array of {@link PdxInstance PdxInstances}. + * + * @param json array of {@link Byte#TYPE bytes} containing the JSON to convert to PDX. + * @return an array of {@link PdxInstance PdxInstances} for each JSON object. + * @see org.apache.geode.pdx.PdxInstance + * @see #getJsonToPdxArrayConverter() + */ + protected @NonNull PdxInstance[] toPdxArray(@NonNull byte[] json) { + return getJsonToPdxArrayConverter().convert(json); + } } diff --git a/spring-geode/src/test/java/org/springframework/geode/data/json/JsonCacheDataImporterExporterUnitTests.java b/spring-geode/src/test/java/org/springframework/geode/data/json/JsonCacheDataImporterExporterUnitTests.java index 13e006fd..da718a08 100644 --- a/spring-geode/src/test/java/org/springframework/geode/data/json/JsonCacheDataImporterExporterUnitTests.java +++ b/spring-geode/src/test/java/org/springframework/geode/data/json/JsonCacheDataImporterExporterUnitTests.java @@ -52,6 +52,8 @@ import org.springframework.context.ApplicationContext; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.data.gemfire.util.ArrayUtils; +import org.springframework.geode.data.json.converter.JsonToPdxArrayConverter; import org.springframework.geode.data.json.converter.JsonToPdxConverter; import org.springframework.geode.data.json.converter.ObjectToJsonConverter; @@ -67,6 +69,7 @@ import example.app.pos.model.LineItem; * @see org.mockito.junit.MockitoJUnitRunner * @see org.apache.geode.cache.Region * @see org.apache.geode.pdx.PdxInstance + * @see org.springframework.context.ApplicationContext * @see org.springframework.core.io.Resource * @see org.springframework.geode.data.json.JsonCacheDataImporterExporter * @since 1.3.0 @@ -79,27 +82,27 @@ public class JsonCacheDataImporterExporterUnitTests { @Test @SuppressWarnings("unchecked") - public void doExportFromRegionLogsAndSavesJson() { + public void doExportFromRegionSavesJson() { - String json = "[{ \"name\": \"Jon Doe\"}]"; + String json = "[{ \"name\": \"Jon Doe\"}, { \"name\": \"Jane Doe\" }]"; Resource mockResource = mock(Resource.class); Region mockRegion = mock(Region.class); doReturn("TestRegion").when(mockRegion).getName(); - - doReturn(JsonCacheDataImporterExporter.FILESYSTEM_RESOURCE_PREFIX).when(importer).getResourceLocation(); - doReturn(Optional.of(mockResource)).when(this.importer).getResource(eq(mockRegion), - eq(JsonCacheDataImporterExporter.FILESYSTEM_RESOURCE_PREFIX)); + doReturn(Optional.of(mockResource)).when(this.importer).getResource(eq(mockRegion), anyString()); + doReturn(JsonCacheDataImporterExporter.FILESYSTEM_RESOURCE_PREFIX + "/path/to/data.json") + .when(importer).getResourceLocation(); doNothing().when(this.importer).save(anyString(), any(Resource.class)); doReturn(json).when(this.importer).toJson(any()); assertThat(this.importer.doExportFrom(mockRegion)).isEqualTo(mockRegion); verify(this.importer, times(1)).toJson(eq(mockRegion)); - verify(this.importer, times(1)) - .getResource(eq(mockRegion), eq(JsonCacheDataImporterExporter.FILESYSTEM_RESOURCE_PREFIX)); + verify(this.importer, times(1)).getResource(eq(mockRegion), + eq(JsonCacheDataImporterExporter.FILESYSTEM_RESOURCE_PREFIX + "/path/to/data.json")); + verify(this.importer, times(1)).getResourceLocation(); verify(this.importer, times(1)).save(eq(json), eq(mockResource)); } @@ -134,6 +137,7 @@ public class JsonCacheDataImporterExporterUnitTests { assertThat(writer.toString()).isEqualTo(json); verify(this.importer, times(1)).newWriter(eq(mockResource)); + verifyNoInteractions(mockResource); } @Test(expected = DataAccessResourceFailureException.class) @@ -146,7 +150,7 @@ public class JsonCacheDataImporterExporterUnitTests { Resource mockResource = mock(Resource.class); - doReturn("/path/to/resource.json").when(mockResource).getDescription(); + doReturn("/path/to/data.json").when(mockResource).getDescription(); doReturn(mockWriter).when(this.importer).newWriter(eq(mockResource)); doThrow(new IOException("TEST")).when(mockWriter).write(anyString(), anyInt(), anyInt()); @@ -156,7 +160,7 @@ public class JsonCacheDataImporterExporterUnitTests { catch (DataAccessResourceFailureException expected) { assertThat(expected) - .hasMessageStartingWith("Failed to save content '%s' to Resource [/path/to/resource.json]", + .hasMessageStartingWith("Failed to save content '%s' to Resource [/path/to/data.json]", this.importer.formatContentForPreview(json)); assertThat(expected).hasCauseInstanceOf(IOException.class); assertThat(expected.getCause()).hasMessage("TEST"); @@ -167,10 +171,12 @@ public class JsonCacheDataImporterExporterUnitTests { finally { verify(this.importer, times(1)).newWriter(eq(mockResource)); verify(mockWriter, times(1)).write(eq(json), eq(0), eq(json.length())); + verify(mockWriter, never()).flush(); + verify(mockWriter, times(1)).close(); } } @Test - public void saveWithNoContent() throws IOException { + public void saveWithNoContent() { Resource mockResource = mock(Resource.class); @@ -178,7 +184,8 @@ public class JsonCacheDataImporterExporterUnitTests { this.importer.save(" ", mockResource); } finally { - verify(this.importer, never()).newWriter(any(Resource.class)); + verify(this.importer, times(1)).save(eq(" "), eq(mockResource)); + verifyNoMoreInteractions(this.importer); verifyNoInteractions(mockResource); } } @@ -196,6 +203,10 @@ public class JsonCacheDataImporterExporterUnitTests { throw expected; } + finally { + verify(this.importer, times(1)).save(eq("{}"), isNull()); + verifyNoMoreInteractions(this.importer); + } } @Test @@ -206,26 +217,30 @@ public class JsonCacheDataImporterExporterUnitTests { Region mockRegion = mock(Region.class); - PdxInstance mockPdxInstance = mock(PdxInstance.class); + PdxInstance mockPdxInstanceOne = mock(PdxInstance.class); + PdxInstance mockPdxInstanceTwo = mock(PdxInstance.class); - byte[] json = "{ \"name\": \"Jon Doe\"}".getBytes(); + byte[] json = "[{ \"name\": \"Jon Doe\"}, { \"name\": \"Jane Doe\" }]".getBytes(); doReturn(Optional.of(mockResource)).when(this.importer).getResource(eq(mockRegion), eq(JsonCacheDataImporterExporter.CLASSPATH_RESOURCE_PREFIX)); doReturn(true).when(mockResource).exists(); doReturn(json).when(this.importer).getContent(eq(mockResource)); - doReturn(mockPdxInstance).when(this.importer).toPdx(eq(json)); - doReturn(1).when(this.importer).getIdentifier(eq(mockPdxInstance)); + doReturn(ArrayUtils.asArray(mockPdxInstanceOne, mockPdxInstanceTwo)).when(this.importer).toPdxArray(eq(json)); + doReturn(1).when(this.importer).getIdentifier(eq(mockPdxInstanceOne)); + doReturn(2).when(this.importer).getIdentifier(eq(mockPdxInstanceTwo)); assertThat(this.importer.doImportInto(mockRegion)).isEqualTo(mockRegion); verify(this.importer, times(1)) .getResource(eq(mockRegion), eq(JsonCacheDataImporterExporter.CLASSPATH_RESOURCE_PREFIX)); verify(this.importer, times(1)).getContent(eq(mockResource)); - verify(this.importer, times(1)).toPdx(eq(json)); - verify(this.importer, times(1)).getIdentifier(eq(mockPdxInstance)); + verify(this.importer, times(1)).toPdxArray(eq(json)); + verify(this.importer, times(1)).getIdentifier(eq(mockPdxInstanceOne)); + verify(this.importer, times(1)).getIdentifier(eq(mockPdxInstanceTwo)); verify(mockResource, times(1)).exists(); - verify(mockRegion, times(1)).put(eq(1), eq(mockPdxInstance)); + verify(mockRegion, times(1)).put(eq(1), eq(mockPdxInstanceOne)); + verify(mockRegion, times(1)).put(eq(2), eq(mockPdxInstanceTwo)); } @Test @@ -308,7 +323,7 @@ public class JsonCacheDataImporterExporterUnitTests { .getResource(eq(mockRegion), eq(JsonCacheDataImporterExporter.CLASSPATH_RESOURCE_PREFIX)); doReturn(true).when(mockResource).exists(); doReturn(json).when(this.importer).getContent(eq(mockResource)); - doReturn(null).when(this.importer).toPdx(eq(json)); + doReturn(new PdxInstance[0]).when(this.importer).toPdxArray(eq(json)); assertThat(this.importer.doImportInto(mockRegion)).isEqualTo(mockRegion); @@ -316,7 +331,7 @@ public class JsonCacheDataImporterExporterUnitTests { verify(this.importer, times(1)) .getResource(eq(mockRegion), eq(JsonCacheDataImporterExporter.CLASSPATH_RESOURCE_PREFIX)); verify(this.importer, times(1)).getContent(eq(mockResource)); - verify(this.importer, times(1)).toPdx(eq(json)); + verify(this.importer, times(1)).toPdxArray(eq(json)); verify(mockResource, times(1)).exists(); verifyNoMoreInteractions(this.importer); verifyNoMoreInteractions(mockResource); @@ -434,9 +449,9 @@ public class JsonCacheDataImporterExporterUnitTests { doReturn(Arrays.asList("", "age", null, "name", " ")).when(mockPdxInstance).getFieldNames(); doReturn(false).when(mockPdxInstance).isIdentityField(anyString()); - doReturn(101).when(this.importer).getIdField(eq(mockPdxInstance)); + doReturn(99).when(this.importer).getIdField(eq(mockPdxInstance)); - assertThat(this.importer.getIdentifier(mockPdxInstance)).isEqualTo(101); + assertThat(this.importer.getIdentifier(mockPdxInstance)).isEqualTo(99); verify(this.importer, times(1)).getIdField(eq(mockPdxInstance)); verify(mockPdxInstance, times(1)).getFieldNames(); @@ -448,21 +463,6 @@ public class JsonCacheDataImporterExporterUnitTests { verify(mockPdxInstance, never()).getField(anyString()); } - @Test(expected = IllegalArgumentException.class) - public void getIdentifierFromNullPdxInstance() { - - try { - this.importer.getIdentifier(null); - } - catch (IllegalArgumentException expected) { - - assertThat(expected).hasMessage("PdxInstance must not be null"); - assertThat(expected).hasNoCause(); - - throw expected; - } - } - @Test(expected = IllegalStateException.class) public void getIdentifierFromPdxInstanceWithNoIdentifier() { @@ -489,6 +489,21 @@ public class JsonCacheDataImporterExporterUnitTests { } } + @Test(expected = IllegalArgumentException.class) + public void getIdentifierFromNullPdxInstance() { + + try { + this.importer.getIdentifier(null); + } + catch (IllegalArgumentException expected) { + + assertThat(expected).hasMessage("PdxInstance must not be null"); + assertThat(expected).hasNoCause(); + + throw expected; + } + } + @Test public void getIdFieldFromPdxInstance() { @@ -505,21 +520,6 @@ public class JsonCacheDataImporterExporterUnitTests { .getField(eq(JsonCacheDataImporterExporter.ID_FIELD_NAME)); } - @Test(expected = IllegalArgumentException.class) - public void getIdFieldFromNullPdxInstance() { - - try { - this.importer.getIdField(null); - } - catch (IllegalArgumentException expected) { - - assertThat(expected).hasMessage("PdxInstance must not be null"); - assertThat(expected).hasNoCause(); - - throw expected; - } - } - @Test(expected = IllegalStateException.class) public void getIdFieldFromPdxInstanceWithNoIdField() { @@ -576,6 +576,21 @@ public class JsonCacheDataImporterExporterUnitTests { } } + @Test(expected = IllegalArgumentException.class) + public void getIdFieldFromNullPdxInstance() { + + try { + this.importer.getIdField(null); + } + catch (IllegalArgumentException expected) { + + assertThat(expected).hasMessage("PdxInstance must not be null"); + assertThat(expected).hasNoCause(); + + throw expected; + } + } + @Test public void getResourceFromRegionAndResourcePrefix() { @@ -739,4 +754,26 @@ public class JsonCacheDataImporterExporterUnitTests { verify(this.importer, times(1)).getJsonToPdxConverter(); verify(mockConverter, times(1)).convert(eq(json)); } + + @Test + public void toPdxArrayFromJsonCallsJsonToPdxArrayConverter() { + + byte[] json = "[{ \"name\": \"Jon Doe\" }, { \"name\": \"Jane Doe\" }]".getBytes(); + + JsonToPdxArrayConverter mockConverter = mock(JsonToPdxArrayConverter.class); + + PdxInstance mockPdxInstanceOne = mock(PdxInstance.class); + PdxInstance mockPdxInstanceTwo = mock(PdxInstance.class); + + PdxInstance[] pdxArray = ArrayUtils.asArray(mockPdxInstanceOne, mockPdxInstanceTwo); + + doReturn(mockConverter).when(this.importer).getJsonToPdxArrayConverter(); + doReturn(pdxArray).when(mockConverter).convert(eq(json)); + + assertThat(this.importer.toPdxArray(json)).isEqualTo(pdxArray); + + verify(this.importer, times(1)).getJsonToPdxArrayConverter(); + verify(mockConverter, times(1)).convert(eq(json)); + + } }