diff --git a/src/test/data/Union/MandatoryConstraintThis.ili b/src/test/data/Union/MandatoryConstraintThis.ili
new file mode 100644
index 0000000..c3210e9
--- /dev/null
+++ b/src/test/data/Union/MandatoryConstraintThis.ili
@@ -0,0 +1,30 @@
+INTERLIS 2.4;
+
+MODEL TestSuite
+ AT "mailto:info@geowerkstatt.ch" VERSION "2023-12-13" =
+ IMPORTS GeoW_FunctionsExt;
+
+ DOMAIN
+ !!@CRS=EPSG:2056
+ CHKoord = COORD 2460000.000 .. 2870000.000 [INTERLIS.m],
+ 1045000.000 .. 1310000.000 [INTERLIS.m],
+ ROTATION 2 -> 1;
+
+ Surface = SURFACE WITH (STRAIGHTS) VERTEX CHKoord WITHOUT OVERLAPS > 0.1;
+
+ TOPIC FunctionTestTopic =
+
+ CLASS BaseClass =
+ surfaceAttribute : BAG OF Surface;
+
+ MANDATORY CONSTRAINT falseConstraint:
+ GeoW_FunctionsExt.GetInnerRingsCount(GeoW_FunctionsExt.Union(THIS->surfaceAttribute, UNDEFINED), UNDEFINED) > 3;
+
+ MANDATORY CONSTRAINT oneInnerRingConstraint:
+ GeoW_FunctionsExt.GetInnerRingsCount(GeoW_FunctionsExt.Union(THIS->surfaceAttribute, UNDEFINED), UNDEFINED) == 1;
+
+ END BaseClass;
+
+ END FunctionTestTopic;
+
+END TestSuite.
diff --git a/src/test/data/Union/SetConstraintAll.ili b/src/test/data/Union/SetConstraintAll.ili
new file mode 100644
index 0000000..472a3d6
--- /dev/null
+++ b/src/test/data/Union/SetConstraintAll.ili
@@ -0,0 +1,31 @@
+INTERLIS 2.4;
+
+MODEL TestSuite
+ AT "mailto:info@geowerkstatt.ch" VERSION "2023-12-13" =
+ IMPORTS GeoW_FunctionsExt;
+
+ DOMAIN
+ !!@CRS=EPSG:2056
+ CHKoord = COORD 2460000.000 .. 2870000.000 [INTERLIS.m],
+ 1045000.000 .. 1310000.000 [INTERLIS.m],
+ ROTATION 2 -> 1;
+
+ TOPIC FunctionTestTopic =
+
+ CLASS BaseClass =
+ surfaceAttribute : SURFACE WITH (STRAIGHTS) VERTEX CHKoord WITHOUT OVERLAPS > 0.1;
+
+ SET CONSTRAINT falseConstraint:
+ GeoW_FunctionsExt.GetInnerRingsCount(ALL, "surfaceAttribute") > 3;
+ SET CONSTRAINT falseConstraintUnion:
+ GeoW_FunctionsExt.GetInnerRingsCount(GeoW_FunctionsExt.Union(ALL, "surfaceAttribute"), UNDEFINED) > 3;
+
+ SET CONSTRAINT trueConstraint:
+ GeoW_FunctionsExt.GetInnerRingsCount(ALL, "surfaceAttribute") == 1;
+ SET CONSTRAINT trueConstraintUnion:
+ GeoW_FunctionsExt.GetInnerRingsCount(GeoW_FunctionsExt.Union(ALL, "surfaceAttribute"), UNDEFINED) == 1;
+ END BaseClass;
+
+ END FunctionTestTopic;
+
+END TestSuite.
diff --git a/src/test/data/Union/SetConstraintMergedInnerRings.ili b/src/test/data/Union/SetConstraintMergedInnerRings.ili
new file mode 100644
index 0000000..379c702
--- /dev/null
+++ b/src/test/data/Union/SetConstraintMergedInnerRings.ili
@@ -0,0 +1,29 @@
+INTERLIS 2.4;
+
+MODEL TestSuite
+ AT "mailto:info@geowerkstatt.ch" VERSION "2023-12-13" =
+ IMPORTS GeoW_FunctionsExt;
+
+ DOMAIN
+ Coord = COORD 0 .. 10 [INTERLIS.m],
+ 0 .. 10 [INTERLIS.m],
+ ROTATION 2 -> 1;
+
+ TOPIC FunctionTestTopic =
+
+ CLASS BaseClass =
+ surfaceAttribute : SURFACE WITH (STRAIGHTS) VERTEX Coord WITHOUT OVERLAPS > 0.1;
+
+ !! Surfaces evaluated separately
+ SET CONSTRAINT innerRings:
+ GeoW_FunctionsExt.GetInnerRingsCount(ALL, "surfaceAttribute") == 2;
+
+ !! Surfaces evaluated as union (inner rings overlap in test data)
+ SET CONSTRAINT innerRingsUnion:
+ GeoW_FunctionsExt.GetInnerRingsCount(GeoW_FunctionsExt.Union(ALL, "surfaceAttribute"), UNDEFINED) == 1;
+
+ END BaseClass;
+
+ END FunctionTestTopic;
+
+END TestSuite.
diff --git a/src/test/data/Union/TestData.xtf b/src/test/data/Union/TestData.xtf
new file mode 100644
index 0000000..1c659aa
--- /dev/null
+++ b/src/test/data/Union/TestData.xtf
@@ -0,0 +1,101 @@
+
+
+
+
+ GeoW_FunctionsExt
+ TestSuite
+
+ ili2gpkg-4.6.1-63db90def1260a503f0f2d4cb846686cd4851184
+
+
+
+
+
+
+
+
+
+ 2530000
+ 1150000
+
+
+ 2530010
+ 1150000
+
+
+ 2530010
+ 1150010
+
+
+ 2530000
+ 1150010
+
+
+ 2530000
+ 1150000
+
+
+
+
+
+
+ 2530003
+ 1150003
+
+
+ 2530006
+ 1150003
+
+
+ 2530006
+ 1150006
+
+
+ 2530003
+ 1150006
+
+
+ 2530003
+ 1150003
+
+
+
+
+
+
+
+
+
+
+
+
+ 2646349.508
+ 1249022.931
+
+
+ 2646353.508
+ 1249022.926
+
+
+ 2646353.503
+ 1249018.926
+
+
+ 2646349.503
+ 1249018.931
+
+
+ 2646349.508
+ 1249022.931
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/data/Union/TestDataMergedInnerRings.xtf b/src/test/data/Union/TestDataMergedInnerRings.xtf
new file mode 100644
index 0000000..64e9430
--- /dev/null
+++ b/src/test/data/Union/TestDataMergedInnerRings.xtf
@@ -0,0 +1,125 @@
+
+
+
+
+ GeoW_FunctionsExt
+ TestSuite
+
+ ili2gpkg-4.6.1-63db90def1260a503f0f2d4cb846686cd4851184
+
+
+
+
+
+
+
+
+
+ 0
+ 0
+
+
+ 0
+ 10
+
+
+ 10
+ 10
+
+
+ 10
+ 0
+
+
+ 0
+ 0
+
+
+
+
+
+
+ 1
+ 1
+
+
+ 1
+ 2
+
+
+ 2
+ 2
+
+
+ 2
+ 1
+
+
+ 1
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+ 0
+
+
+ 0
+ 10
+
+
+ 10
+ 10
+
+
+ 10
+ 0
+
+
+ 0
+ 0
+
+
+
+
+
+
+ 1
+ 1
+
+
+ 1
+ 4
+
+
+ 4
+ 4
+
+
+ 4
+ 1
+
+
+ 1
+ 1
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/java/ch/geowerkstatt/ilivalidator/extensions/functions/UnionIoxPluginTest.java b/src/test/java/ch/geowerkstatt/ilivalidator/extensions/functions/UnionIoxPluginTest.java
new file mode 100644
index 0000000..4b172e2
--- /dev/null
+++ b/src/test/java/ch/geowerkstatt/ilivalidator/extensions/functions/UnionIoxPluginTest.java
@@ -0,0 +1,47 @@
+package ch.geowerkstatt.ilivalidator.extensions.functions;
+
+import ch.interlis.ili2c.Ili2cFailure;
+import ch.interlis.iox.IoxException;
+import com.vividsolutions.jts.util.Assert;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public final class UnionIoxPluginTest {
+ private static final String TEST_DATA = "Union/TestData.xtf";
+ private static final String TEST_DATA_MERGED_INNER_RINGS = "Union/TestDataMergedInnerRings.xtf";
+ private ValidationTestHelper vh;
+
+ @BeforeEach
+ void setUp() {
+ vh = new ValidationTestHelper();
+ vh.addFunction(new GetInnerRingsCountIoxPlugin());
+ vh.addFunction(new UnionIoxPlugin());
+ }
+
+ @Test
+ void mandatoryConstraintOnThis() throws Ili2cFailure, IoxException {
+ vh.runValidation(new String[]{TEST_DATA}, new String[]{"Union/MandatoryConstraintThis.ili"});
+ Assert.equals(3, vh.getErrs().size());
+ AssertionHelper.assertConstraintErrors(vh, 2, "falseConstraint");
+ AssertionHelper.assertConstraintErrors(vh, 0, "0", "oneInnerRingConstraint");
+ AssertionHelper.assertConstraintErrors(vh, 1, "1", "oneInnerRingConstraint");
+ }
+
+ @Test
+ void setConstraintAll() throws Ili2cFailure, IoxException {
+ vh.runValidation(new String[]{TEST_DATA}, new String[]{"Union/SetConstraintAll.ili"});
+ Assert.equals(2, vh.getErrs().size());
+ AssertionHelper.assertConstraintErrors(vh, 1, "falseConstraint");
+ AssertionHelper.assertConstraintErrors(vh, 1, "falseConstraintUnion");
+ AssertionHelper.assertNoConstraintError(vh, "trueConstraint");
+ AssertionHelper.assertNoConstraintError(vh, "trueConstraintUnion");
+ }
+
+ @Test
+ void setConstraintMergedInnerRings() throws Ili2cFailure, IoxException {
+ vh.runValidation(new String[]{TEST_DATA_MERGED_INNER_RINGS}, new String[]{"Union/SetConstraintMergedInnerRings.ili"});
+ Assert.equals(0, vh.getErrs().size());
+ AssertionHelper.assertNoConstraintError(vh, "innerRings");
+ AssertionHelper.assertNoConstraintError(vh, "innerRingsUnion");
+ }
+}