diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/NetcdfUtils.java b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/NetcdfUtils.java
index 945557ca5b6bae9a54f60431981b66d9c2f5a225..dfd4f874f7117dca03d0f22a3383826bc1d3dd2a 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/NetcdfUtils.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/NetcdfUtils.java
@@ -18,6 +18,9 @@ import ucar.nc2.Group;
 
 public class NetcdfUtils {
 
+  // Tolerance for longitude/latitude comparisons
+  static final double LOCATION_TOLERANCE = 0.000001;
+
   /**
    * Creates a border going clockwise of the given longitudes and latitudes.
    * 
@@ -32,15 +35,16 @@ public class NetcdfUtils {
       builder.add(lat, longitudes[0]);
     }
 
-    for (var lon : longitudes) {
-      builder.add(latitudes[latitudes.length - 1], lon);
+    // omit duplicate points at corners
+    for (var i = 1; i < longitudes.length; i++) {
+      builder.add(latitudes[latitudes.length - 1], longitudes[i]);
     }
 
-    for (var i = latitudes.length - 1; i >= 0; i--) {
+    for (var i = latitudes.length - 2; i >= 0; i--) {
       builder.add(latitudes[i], longitudes[longitudes.length - 1]);
     }
 
-    for (var i = longitudes.length - 1; i >= 0; i--) {
+    for (var i = longitudes.length - 2; i >= 0; i--) {
       builder.add(latitudes[0], longitudes[i]);
     }
 
@@ -94,7 +98,6 @@ public class NetcdfUtils {
   static int getIdxLTEQ(double[] a, double t) {
     // assumes array is in sorted order (a[i] < a[i+1])
     // make sure target is within the range of the array
-    var tol = 0.000001;
     var n = a.length;
 
     if (t < a[0] || t > a[n - 1]) {
@@ -105,11 +108,11 @@ public class NetcdfUtils {
     }
 
     // if (t == a[0]) {
-    if (DoubleMath.fuzzyEquals(a[0], t, tol)) {
+    if (DoubleMath.fuzzyEquals(a[0], t, LOCATION_TOLERANCE)) {
       return 0;
     }
     // if (t == a[n - 1]) {
-    if (DoubleMath.fuzzyEquals(a[n - 1], t, tol)) {
+    if (DoubleMath.fuzzyEquals(a[n - 1], t, LOCATION_TOLERANCE)) {
       return n - 2; // return second to last index number
     }
 
@@ -129,12 +132,10 @@ public class NetcdfUtils {
    * Calculate fractional distance from a1 to t, between a1 and a2
    */
   static double calcFrac(double a1, double a2, double t) {
-    var tol = 0.00001;
-
-    if (Math.abs(t - a1) < tol) {
+    if (Math.abs(t - a1) < LOCATION_TOLERANCE) {
       // target value == a1
       return 0.0;
-    } else if (Math.abs(t - a2) < tol) {
+    } else if (Math.abs(t - a2) < LOCATION_TOLERANCE) {
       // target value == a2
       return 1.0;
     } else {
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/StaticHazard.java b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/StaticHazard.java
index fa202d9d019e233979ab9e88de95d4e3d0922a47..5ee1f26dd08af76d6968f48b29821438a4d82884 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/StaticHazard.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/StaticHazard.java
@@ -3,7 +3,6 @@ package gov.usgs.earthquake.nshmp.netcdf.reader;
 import static com.google.common.base.Preconditions.checkState;
 
 import java.util.EnumMap;
-import java.util.Map;
 
 import gov.usgs.earthquake.nshmp.data.XySequence;
 import gov.usgs.earthquake.nshmp.gmm.Imt;
@@ -20,9 +19,10 @@ public class StaticHazard extends EnumMap<Imt, XySequence> {
     putAll(staticHazard);
   }
 
-  public Map<Imt, XySequence> staticHazard() {
-    return Map.copyOf(this);
-  }
+  // unnecessary method?
+  // public Map<Imt, XySequence> staticHazard() {
+  // return Map.copyOf(this);
+  // }
 
   public static Builder builder() {
     return new Builder();
diff --git a/src/test/java/gov/usgs/earthquake/nshmp/netcdf/NshmGroupTests.java b/src/test/java/gov/usgs/earthquake/nshmp/netcdf/NshmGroupTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..a86458b65261c848d902a6593ec0871daf379a07
--- /dev/null
+++ b/src/test/java/gov/usgs/earthquake/nshmp/netcdf/NshmGroupTests.java
@@ -0,0 +1,16 @@
+package gov.usgs.earthquake.nshmp.netcdf;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+class NshmGroupTests {
+
+  @Test
+  final void testCONUS_2018() {
+    assertEquals(NshmGroup.CONUS_2018.toString(), "2018 Conterminous U.S. NSHM");
+    assertEquals(NshmGroup.CONUS_2018.baseGroup(), "/CONUS/2018/v1.1");
+    assertEquals(NshmGroup.CONUS_2018.locationPrecision(), 2);
+  }
+
+}
diff --git a/src/test/java/gov/usgs/earthquake/nshmp/netcdf/NshmNetcdfReaderTests.java b/src/test/java/gov/usgs/earthquake/nshmp/netcdf/NshmNetcdfReaderTests.java
index 0584f365eab948ee73ac7d531077811c6c5cac09..157504f786b00aeb5e8cb960ffbf7d332651b691 100644
--- a/src/test/java/gov/usgs/earthquake/nshmp/netcdf/NshmNetcdfReaderTests.java
+++ b/src/test/java/gov/usgs/earthquake/nshmp/netcdf/NshmNetcdfReaderTests.java
@@ -20,17 +20,14 @@ import org.junit.jupiter.params.provider.MethodSource;
 import com.google.common.io.Resources;
 
 import gov.usgs.earthquake.nshmp.data.XySequence;
-import gov.usgs.earthquake.nshmp.geo.BorderType;
 import gov.usgs.earthquake.nshmp.geo.Location;
 import gov.usgs.earthquake.nshmp.geo.LocationList;
-import gov.usgs.earthquake.nshmp.geo.Regions;
 import gov.usgs.earthquake.nshmp.gmm.Imt;
 import gov.usgs.earthquake.nshmp.netcdf.reader.BoundingHazards;
-import gov.usgs.earthquake.nshmp.netcdf.reader.NetcdfUtils;
 import gov.usgs.earthquake.nshmp.netcdf.reader.StaticHazard;
 import gov.usgs.earthquake.nshmp.netcdf.reader.StaticHazards;
 
-class NshmNetcdfReaderTests {
+public class NshmNetcdfReaderTests {
 
   static final String CONUS_TEST_FILE = "conus-test.nc";
   static final Path NETCDF_PATH = Paths.get(Resources.getResource(CONUS_TEST_FILE).getPath());
@@ -41,13 +38,14 @@ class NshmNetcdfReaderTests {
 
   // difference tolerance, until we can incorporate precision into
   // NshmNetcdfReader
-  static final double IML_TOL = 1e-6;
+  public static final double IML_TOL = 1e-6;
   static final double HAZ_TOL = 1e-8;
 
   static final Location TARGET_LOCATION = Location.create(39.213, -105.234);
 
-  static final double[] EXPECTED_LONGITUDES = new double[] { -105.3, -105.25, -105.2, -105.15 };
-  static final double[] EXPECTED_LATITUDES = new double[] { 39.15, 39.2, 39.25, 39.3 };
+  public static final double[] EXPECTED_LONGITUDES =
+      new double[] { -105.3, -105.25, -105.2, -105.15 };
+  public static final double[] EXPECTED_LATITUDES = new double[] { 39.15, 39.2, 39.25, 39.3 };
 
   static final LocationList BOUNDING_LOCATIONS = LocationList.builder()
       .add(EXPECTED_LATITUDES[1], EXPECTED_LONGITUDES[1])
@@ -87,8 +85,8 @@ class NshmNetcdfReaderTests {
       new double[] { 4.1153710E-02, 3.8681961E-03, 1.2028426E-04, 3.5812084E-07 },
       new double[] { 5.2503492E-02, 3.4418438E-03, 4.0267402E-05, 4.5693341E-08 });
 
-  static final List<SiteClass> SITE_CLASSES = List.of(SiteClass.CD, SiteClass.C);
-  static final List<Imt> IMTS = List.of(Imt.PGA, Imt.SA0P4);
+  public static final List<SiteClass> SITE_CLASSES = List.of(SiteClass.CD, SiteClass.C);
+  public static final List<Imt> IMTS = List.of(Imt.PGA, Imt.SA0P4);
 
   static final int TARGET_LOWER_LEFT_LONGITUDE_IDX = 1;
   static final int TARGET_LOWER_LEFT_LATITUDE_IDX = 1;
@@ -96,11 +94,12 @@ class NshmNetcdfReaderTests {
   static final double TARGET_LONGITUDE_FRAC;
   static final double TARGET_LATITUDE_FRAC;
 
-  static Map<SiteClass, Map<Imt, double[]>> IMLS = new HashMap<>();
+  public static Map<SiteClass, Map<Imt, double[]>> IMLS = new HashMap<>();
 
   static BoundingHazards BOUNDING_HAZARDS;
 
-  static final NshmNetcdfReader NETCDF = new NshmNetcdfReader(NshmGroup.CONUS_2018, NETCDF_PATH);
+  public static final NshmNetcdfReader NETCDF =
+      new NshmNetcdfReader(NshmGroup.CONUS_2018, NETCDF_PATH);
 
   static {
     var builder = BoundingHazards.builder();
@@ -154,57 +153,6 @@ class NshmNetcdfReaderTests {
     assertTrue(NshmGroup.CONUS_2018.equals(NETCDF.nshmGroup()));
   }
 
-  @Test
-  final void coordinatesTests() {
-    var coords = NETCDF.coordinates();
-
-    // Check locations
-    assertArrayEquals(EXPECTED_LATITUDES, coords.latitudes());
-    assertArrayEquals(EXPECTED_LONGITUDES, coords.longitudes());
-
-    // Check IMTs
-    assertEquals(IMTS, coords.imts());
-    assertEquals(IMTS.size(), coords.imts().size());
-
-    // Check site classes
-    assertEquals(SITE_CLASSES, coords.siteClasses());
-
-    // Check region
-    var border = NetcdfUtils.buildBorder(EXPECTED_LONGITUDES, EXPECTED_LATITUDES);
-    var expectedRegion = Regions.create("test-region", border, BorderType.MERCATOR_LINEAR);
-    var expectedBorder = expectedRegion.border();
-
-    var actualRegion = coords.region();
-    var actualBorder = actualRegion.border();
-
-    assertEquals(expectedBorder.size(), actualBorder.size());
-    assertEquals(expectedBorder.bounds().max, expectedBorder.bounds().max);
-    assertEquals(expectedBorder.bounds().min, expectedBorder.bounds().min);
-
-    for (var i = 0; i < expectedBorder.size(); i++) {
-      assertEquals(expectedBorder.get(i), actualBorder.get(i));
-    }
-
-    // Check IMLs
-    var actualImls = coords.imls();
-    assertEquals(IMLS.size(), actualImls.size());
-
-    for (var imlEntry : IMLS.entrySet()) {
-      var siteClass = imlEntry.getKey();
-      assertTrue(actualImls.containsKey(siteClass));
-      assertEquals(imlEntry.getValue().size(), actualImls.get(siteClass).size());
-
-      for (var siteEntry : imlEntry.getValue().entrySet()) {
-        var imt = siteEntry.getKey();
-        var expectedValue = siteEntry.getValue();
-        var actualValue = actualImls.get(siteClass).get(imt);
-
-        assertTrue(actualImls.get(siteClass).containsKey(imt));
-        assertArrayEquals(expectedValue, actualValue, IML_TOL);
-      }
-    }
-  }
-
   @Test
   final void pathTests() {
     assertEquals(NETCDF_PATH, NETCDF.path());
diff --git a/src/test/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingHazardsTests.java b/src/test/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingHazardsTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..ea5864ed9e6bea5e774a517fad767b5ac8b744ec
--- /dev/null
+++ b/src/test/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingHazardsTests.java
@@ -0,0 +1,17 @@
+package gov.usgs.earthquake.nshmp.netcdf.reader;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+class BoundingHazardsTests {
+
+  @Test
+  final void builderTest() {
+    // calling build() on empty builder should fail
+    assertThrows(IllegalStateException.class, () -> {
+      var boundingHazards = BoundingHazards.builder().build();
+    });
+  }
+
+}
diff --git a/src/test/java/gov/usgs/earthquake/nshmp/netcdf/reader/NetcdfCoordinatesTest.java b/src/test/java/gov/usgs/earthquake/nshmp/netcdf/reader/NetcdfCoordinatesTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..bde5ef19762bd22cdc898679c9d40f515671f08b
--- /dev/null
+++ b/src/test/java/gov/usgs/earthquake/nshmp/netcdf/reader/NetcdfCoordinatesTest.java
@@ -0,0 +1,68 @@
+package gov.usgs.earthquake.nshmp.netcdf.reader;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+import gov.usgs.earthquake.nshmp.geo.BorderType;
+import gov.usgs.earthquake.nshmp.geo.Regions;
+import gov.usgs.earthquake.nshmp.netcdf.NshmNetcdfReaderTests;
+
+class NetcdfCoordinatesTest {
+
+  @Test
+  final void coordinatesTests() {
+    var coords = NshmNetcdfReaderTests.NETCDF.coordinates();
+
+    // Check locations
+    assertArrayEquals(NshmNetcdfReaderTests.EXPECTED_LATITUDES, coords.latitudes());
+    assertArrayEquals(NshmNetcdfReaderTests.EXPECTED_LONGITUDES, coords.longitudes());
+
+    // Check IMTs
+    assertEquals(NshmNetcdfReaderTests.IMTS, coords.imts());
+    assertEquals(NshmNetcdfReaderTests.IMTS.size(), coords.imts().size());
+
+    // Check site classes
+    assertEquals(NshmNetcdfReaderTests.SITE_CLASSES, coords.siteClasses());
+
+    // Check region
+    var border = NetcdfUtils.buildBorder(
+        NshmNetcdfReaderTests.EXPECTED_LONGITUDES,
+        NshmNetcdfReaderTests.EXPECTED_LATITUDES);
+    var expectedRegion = Regions.create("test-region", border, BorderType.MERCATOR_LINEAR);
+    var expectedBorder = expectedRegion.border();
+
+    var actualRegion = coords.region();
+    var actualBorder = actualRegion.border();
+
+    assertEquals(expectedBorder.size(), actualBorder.size());
+    assertEquals(expectedBorder.bounds().max, actualBorder.bounds().max);
+    assertEquals(expectedBorder.bounds().min, actualBorder.bounds().min);
+
+    for (var i = 0; i < expectedBorder.size(); i++) {
+      assertEquals(expectedBorder.get(i), actualBorder.get(i));
+    }
+
+    // Check IMLs
+    var actualImls = coords.imls();
+    assertEquals(NshmNetcdfReaderTests.IMLS.size(), actualImls.size());
+
+    for (var imlEntry : NshmNetcdfReaderTests.IMLS.entrySet()) {
+      var siteClass = imlEntry.getKey();
+      assertTrue(actualImls.containsKey(siteClass));
+      assertEquals(imlEntry.getValue().size(), actualImls.get(siteClass).size());
+
+      for (var siteEntry : imlEntry.getValue().entrySet()) {
+        var imt = siteEntry.getKey();
+        var expectedValue = siteEntry.getValue();
+        var actualValue = actualImls.get(siteClass).get(imt);
+
+        assertTrue(actualImls.get(siteClass).containsKey(imt));
+        assertArrayEquals(expectedValue, actualValue, NshmNetcdfReaderTests.IML_TOL);
+      }
+    }
+  }
+
+}
diff --git a/src/test/java/gov/usgs/earthquake/nshmp/netcdf/reader/NetcdfUtilsTests.java b/src/test/java/gov/usgs/earthquake/nshmp/netcdf/reader/NetcdfUtilsTests.java
index 0f7357d6bb1f9391bcff56c17d1705ffd9d66fab..9e0f818a5856aac68584779c430ed4d6dbb57100 100644
--- a/src/test/java/gov/usgs/earthquake/nshmp/netcdf/reader/NetcdfUtilsTests.java
+++ b/src/test/java/gov/usgs/earthquake/nshmp/netcdf/reader/NetcdfUtilsTests.java
@@ -11,6 +11,7 @@ import java.util.List;
 import org.junit.jupiter.api.Test;
 
 import gov.usgs.earthquake.nshmp.data.XySequence;
+import gov.usgs.earthquake.nshmp.geo.LocationList;
 import gov.usgs.earthquake.nshmp.gmm.Imt;
 import gov.usgs.earthquake.nshmp.netcdf.SiteClass;
 
@@ -25,6 +26,10 @@ class NetcdfUtilsTests {
       39.13, 39.14, 39.15, 39.16, 39.17, 39.18, 39.19, 39.20, 39.21, 39.22, 39.23, 39.24, 39.25,
       39.26, 39.27, 39.28, 39.29, 39.30, 39.31, 39.32, 39.33, 39.34, 39.35 };
 
+  private static final double[] BORDER_LONGITUDES = new double[] { 5.0, 6.0 };
+  private static final double[] BORDER_LATITUDES = new double[] { 8.0, 9.0 };
+  private static final LocationList BORDER_LOCATIONS;
+
   private static final double TOL = 1e-7;
 
   static StaticHazards mapHaz0;
@@ -38,6 +43,15 @@ class NetcdfUtilsTests {
   private static final double FRAC = 0.5;
 
   static {
+
+    BORDER_LOCATIONS = LocationList.builder()
+        .add(BORDER_LATITUDES[0], BORDER_LONGITUDES[0])
+        .add(BORDER_LATITUDES[1], BORDER_LONGITUDES[0])
+        .add(BORDER_LATITUDES[1], BORDER_LONGITUDES[1])
+        .add(BORDER_LATITUDES[0], BORDER_LONGITUDES[1])
+        .add(BORDER_LATITUDES[0], BORDER_LONGITUDES[0])
+        .build();
+
     var siteClasses = List.of(SiteClass.B, SiteClass.C, SiteClass.D);
     var imts = List.of(Imt.PGA, Imt.SA0P1, Imt.SA1P5);
     var imlValues = new double[] { 0.1, 0.5, 0.75 };
@@ -106,7 +120,12 @@ class NetcdfUtilsTests {
   }
 
   @Test
-  final void testGetIdxLTEQ() {
+  final void buildBorderTest() {
+    assertEquals(BORDER_LOCATIONS, NetcdfUtils.buildBorder(BORDER_LONGITUDES, BORDER_LATITUDES));
+  }
+
+  @Test
+  final void getIdxLTEQTest() {
     // target is out of range, expect IAE
     assertThrows(IllegalArgumentException.class, () -> {
       NetcdfUtils.getIdxLTEQ(LONGITUDES, -100.0);
@@ -134,7 +153,25 @@ class NetcdfUtilsTests {
   }
 
   @Test
-  final void testCalcGridFrac() {
+  final void calcFracTest() {
+    double bottom = 5.0;
+    double top = bottom + 1.0;
+    double frac = 0.36;
+    assertEquals(0.0, NetcdfUtils.calcFrac(bottom, top, bottom));
+    assertEquals(1.0, NetcdfUtils.calcFrac(bottom, top, top));
+    assertEquals(0.0, NetcdfUtils.calcFrac(
+        bottom,
+        top,
+        bottom + NetcdfUtils.LOCATION_TOLERANCE * 0.9));
+    assertEquals(1.0, NetcdfUtils.calcFrac(
+        bottom,
+        top,
+        top - NetcdfUtils.LOCATION_TOLERANCE * 0.9));
+    assertEquals(frac, NetcdfUtils.calcFrac(bottom, top, bottom + frac), 1e-4);
+  }
+
+  @Test
+  final void calcGridFracTest() {
     double f = 0.13;
     int i = 4;
     assertEquals(f,
@@ -147,7 +184,7 @@ class NetcdfUtilsTests {
   }
 
   @Test
-  final void testLinearInterpolate() {
+  final void linearInterpolateTest() {
     var actual = NetcdfUtils.linearInterpolate(mapHaz0, mapHaz1, FRAC);
     assertTrue(mapHazTarget.keySet().containsAll(actual.keySet()));
 
diff --git a/src/test/java/gov/usgs/earthquake/nshmp/netcdf/reader/StaticHazardTests.java b/src/test/java/gov/usgs/earthquake/nshmp/netcdf/reader/StaticHazardTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..aaba2e59cb25ec881f44d3b00fe533ee66ce368b
--- /dev/null
+++ b/src/test/java/gov/usgs/earthquake/nshmp/netcdf/reader/StaticHazardTests.java
@@ -0,0 +1,17 @@
+package gov.usgs.earthquake.nshmp.netcdf.reader;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+class StaticHazardTests {
+
+  @Test
+  final void builderTest() {
+    // calling build() on empty builder should fail
+    assertThrows(IllegalStateException.class, () -> {
+      var staticHazard = StaticHazard.builder().build();
+    });
+  }
+
+}
diff --git a/src/test/java/gov/usgs/earthquake/nshmp/netcdf/reader/StaticHazardsTests.java b/src/test/java/gov/usgs/earthquake/nshmp/netcdf/reader/StaticHazardsTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..7bedfabc839ab4112ca93dcfcc38ff0f227d3bc6
--- /dev/null
+++ b/src/test/java/gov/usgs/earthquake/nshmp/netcdf/reader/StaticHazardsTests.java
@@ -0,0 +1,17 @@
+package gov.usgs.earthquake.nshmp.netcdf.reader;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+class StaticHazardsTests {
+
+  @Test
+  final void builderTest() {
+    // calling build() on empty builder should fail
+    assertThrows(IllegalStateException.class, () -> {
+      var staticHazards = StaticHazards.builder().build();
+    });
+  }
+
+}