diff --git a/README.md b/README.md
index 50a1209..b6e330f 100644
--- a/README.md
+++ b/README.md
@@ -210,6 +210,57 @@ public async Task HasFix(string testCase)
}
```
+### NavCodeAnalysisBase
+The `NavCodeAnalysisBase` class is a base class for analyzer tests that need to behave differently depending on the version of the AL Language (`Microsoft.Dynamics.Nav.CodeAnalysis`). By inheriting from this class, your tests automatically gain utilities for:
+* Detecting the currently loaded Microsoft.Dynamics.Nav.CodeAnalysis assembly version.
+* Comparing that version against minimum/maximum requirements.
+* Skipping or adjusting tests when features are introduced, changed, or removed between versions.
+
+```CS
+// Example 1: Skip specific test cases based on version
+[TestCase("FeatureOne")]
+[TestCase("FeatureTwo")]
+public void TestFeatures(string testCase)
+{
+ SkipTestIfVersionIsTooLow(
+ ["FeatureOne"],
+ testCase,
+ "15.0.0"
+ );
+
+ SkipTestIfVersionIsTooHigh(
+ ["FeatureTwo"],
+ testCase,
+ "14.9.99"
+ );
+ // Test code
+}
+
+// Example 2: Require minimum version for entire test
+[Test]
+public void TestModernFeature()
+{
+ RequireMinimumVersion("15.0.0", "Requires new API");
+ // Test code that uses version 15+ features
+}
+
+// Example 3: Test feature that only exists in specific range
+[Test]
+public void TestTransitionalFeature()
+{
+ RequireVersionRange("15.0.0", "16.5.0", "Feature deprecated after 16.5");
+ // Test code
+}
+
+// Example 4: Ensure version detection worked
+[Test]
+public void TestVersionDependentBehavior()
+{
+ RequireVersionDetection();
+ // Now safe to use version comparison methods
+}
+```
+
### Basic folder structure
Working with `.al` files instead of declaring the code inline the test method itself, requires a structure. A example for this could be something like this.
diff --git a/src/RoslynTestKit/Helpers/NavCodeAnalysisBase.cs b/src/RoslynTestKit/Helpers/NavCodeAnalysisBase.cs
new file mode 100644
index 0000000..1a47b0f
--- /dev/null
+++ b/src/RoslynTestKit/Helpers/NavCodeAnalysisBase.cs
@@ -0,0 +1,387 @@
+using System;
+using System.Linq;
+using System.Reflection;
+using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics;
+using NUnit.Framework;
+
+namespace RoslynTestKit
+{
+ ///
+ /// NUnit base class that detects the installed Microsoft.Dynamics.Nav.CodeAnalysis (AL DevTools)
+ /// assembly version and provides helpers to conditionally run or skip analyzer tests based on
+ /// version constraints.
+ ///
+ public abstract class NavCodeAnalysisBase
+ {
+ #region Fields
+
+ protected static Version? _navCodeAnalysisVersion;
+ private static readonly Assembly? _navCodeAnalysisAssembly = Assembly.GetAssembly(typeof(DiagnosticAnalyzer));
+
+ #endregion
+
+ #region Initialization
+
+ [OneTimeSetUp]
+ public void BaseSetUp()
+ {
+ RetrieveNavCodeAnalysisVersion();
+ }
+
+ ///
+ /// Retrieves the AssemblyFileVersionAttribute from Microsoft.Dynamics.Nav.CodeAnalysis assembly
+ ///
+ private static void RetrieveNavCodeAnalysisVersion()
+ {
+ var navCodeAnalysisAssembly = _navCodeAnalysisAssembly;
+ if (navCodeAnalysisAssembly == null)
+ {
+ throw new InvalidOperationException("Unable to locate Microsoft.Dynamics.Nav.CodeAnalysis assembly.");
+ }
+
+ var fileVersionAttribute = navCodeAnalysisAssembly.GetCustomAttribute();
+ if (fileVersionAttribute == null)
+ {
+ throw new InvalidOperationException("AssemblyFileVersionAttribute not found in Microsoft.Dynamics.Nav.CodeAnalysis assembly.");
+ }
+
+ if (string.IsNullOrEmpty(fileVersionAttribute.Version))
+ {
+ throw new InvalidOperationException("AssemblyFileVersionAttribute.Version is null or empty in Microsoft.Dynamics.Nav.CodeAnalysis assembly.");
+ }
+
+ if (!Version.TryParse(fileVersionAttribute.Version, out var parsedVersion))
+ {
+ throw new InvalidOperationException($"Unable to parse AssemblyFileVersionAttribute.Version '{fileVersionAttribute.Version}'.");
+ }
+
+ _navCodeAnalysisVersion = parsedVersion;
+
+ TestContext.WriteLine($"Microsoft.Dynamics.Nav.CodeAnalysis version: {_navCodeAnalysisVersion}");
+ }
+
+ #endregion
+
+ #region Version Information Getters
+
+ ///
+ /// Gets the current Nav.CodeAnalysis version as a parsed Version object
+ ///
+ protected static Version? GetNavCodeAnalysisParsed() => _navCodeAnalysisVersion;
+
+ ///
+ /// Checks if the Nav.CodeAnalysis version was successfully detected
+ ///
+ /// True if version information is available, false otherwise
+ protected static bool IsVersionDetected() => _navCodeAnalysisVersion != null;
+
+ #endregion
+
+ #region Version Comparison Methods
+
+ private static Version? TryParseVersion(string version)
+ {
+ return Version.TryParse(version, out var parsedVersion) ? parsedVersion : null;
+ }
+
+ ///
+ /// Checks if the current Nav.CodeAnalysis version is greater than or equal to the specified version
+ ///
+ /// The version to compare against (e.g., "15.0.20")
+ /// True if current version is greater than or equal to the specified version
+ ///
+ /// Use when you need features introduced in a specific version or later:
+ /// if (IsVersionGreaterOrEqual("15.0.20")) { /* use new feature */ }
+ ///
+ protected static bool IsVersionGreaterOrEqual(string version)
+ {
+ var compareVersion = TryParseVersion(version);
+ if (compareVersion == null || _navCodeAnalysisVersion == null)
+ {
+ return false;
+ }
+
+ return _navCodeAnalysisVersion >= compareVersion;
+ }
+
+ ///
+ /// Checks if the current Nav.CodeAnalysis version is less than the specified version
+ ///
+ /// The version to compare against (e.g., "16.0.0")
+ /// True if current version is less than the specified version
+ ///
+ /// Use when excluding tests for newer versions that don't support legacy features:
+ /// if (IsVersionLessThan("16.0.0")) { /* test legacy behavior */ }
+ ///
+ protected static bool IsVersionLessThan(string version)
+ {
+ var compareVersion = TryParseVersion(version);
+ if (compareVersion == null || _navCodeAnalysisVersion == null)
+ {
+ return false;
+ }
+
+ return _navCodeAnalysisVersion < compareVersion;
+ }
+
+ ///
+ /// Checks if the current Nav.CodeAnalysis version is greater than the specified version
+ ///
+ /// The version to compare against (e.g., "15.0.19")
+ /// True if current version is greater than the specified version
+ ///
+ /// Use when you need versions newer than a specific version (excluding that version):
+ /// if (IsVersionGreaterThan("15.0.19")) { /* requires newer than 15.0.19 */ }
+ ///
+ protected static bool IsVersionGreaterThan(string version)
+ {
+ var compareVersion = TryParseVersion(version);
+ if (compareVersion == null || _navCodeAnalysisVersion == null)
+ {
+ return false;
+ }
+
+ return _navCodeAnalysisVersion > compareVersion;
+ }
+
+ ///
+ /// Checks if the current Nav.CodeAnalysis version is less than or equal to the specified version
+ ///
+ /// The version to compare against (e.g., "15.0.20")
+ /// True if current version is less than or equal to the specified version
+ ///
+ /// Use when testing features that were deprecated or changed after a specific version:
+ /// if (IsVersionLessOrEqual("15.0.20")) { /* test behavior up to and including 15.0.20 */ }
+ ///
+ protected static bool IsVersionLessOrEqual(string version)
+ {
+ var compareVersion = TryParseVersion(version);
+ if (compareVersion == null || _navCodeAnalysisVersion == null)
+ {
+ return false;
+ }
+
+ return _navCodeAnalysisVersion <= compareVersion;
+ }
+
+ ///
+ /// Checks if the current Nav.CodeAnalysis version matches a specific version pattern
+ ///
+ /// Major version to match
+ /// Minor version to match (optional)
+ /// True if the version matches the pattern
+ protected static bool IsVersion(int majorVersion, int? minorVersion = null)
+ {
+ if (_navCodeAnalysisVersion == null)
+ {
+ return false;
+ }
+
+ if (minorVersion.HasValue)
+ {
+ return _navCodeAnalysisVersion.Major == majorVersion &&
+ _navCodeAnalysisVersion.Minor == minorVersion.Value;
+ }
+
+ return _navCodeAnalysisVersion.Major == majorVersion;
+ }
+
+ #endregion
+
+ #region Test Helper Methods - Test Case Skipping
+
+ ///
+ /// Skips test cases that require a minimum version if the current version doesn't meet the requirement
+ ///
+ /// Array of test case names that require the minimum version
+ /// The current test case being executed
+ /// The minimum required version (e.g., "15.0.0")
+ /// Optional custom reason for skipping (if null, a default message will be used)
+ ///
+ /// Use in test methods to skip version-specific test cases:
+ ///
+ /// SkipTestIfVersionIsTooLow(
+ /// ["TestCase1", "TestCase2"],
+ /// currentTestCase,
+ /// "15.0.0",
+ /// "Feature requires obsolete table support"
+ /// );
+ ///
+ ///
+ protected static void SkipTestIfVersionIsTooLow(string[] testCases, string currentTestCase, string minimumVersion, string? reason = null)
+ {
+ if (testCases.Contains(currentTestCase) && !IsVersionGreaterOrEqual(minimumVersion))
+ {
+ var message = reason ?? $"Test case requires AL version {minimumVersion} or higher.";
+ Assert.Ignore(message);
+ }
+ }
+
+ ///
+ /// Skips test cases that require a maximum version if the current version exceeds the requirement
+ ///
+ /// Array of test case names that have a maximum version
+ /// The current test case being executed
+ /// The maximum supported version (e.g., "16.0.0")
+ /// Optional custom reason for skipping (if null, a default message will be used)
+ ///
+ /// Use in test methods to skip test cases for versions that are too high:
+ ///
+ /// SkipTestIfVersionIsTooHigh(
+ /// ["LegacyTestCase1", "LegacyTestCase2"],
+ /// currentTestCase,
+ /// "16.0.0",
+ /// "Feature was removed in version 16.0.0"
+ /// );
+ ///
+ ///
+ protected static void SkipTestIfVersionIsTooHigh(string[] testCases, string currentTestCase, string maximumVersion, string? reason = null)
+ {
+ if (testCases.Contains(currentTestCase) && !IsVersionLessOrEqual(maximumVersion))
+ {
+ var message = reason ?? $"Test case requires AL version {maximumVersion} or lower.";
+ Assert.Ignore(message);
+ }
+ }
+
+ ///
+ /// Skips test cases that require a specific version range if the current version is outside that range
+ ///
+ /// Array of test case names that require the version range
+ /// The current test case being executed
+ /// The minimum required version (inclusive, e.g., "15.0.0")
+ /// The maximum supported version (inclusive, e.g., "16.5.0")
+ /// Optional custom reason for skipping (if null, a default message will be used)
+ ///
+ /// Use in test methods to skip test cases that only apply to a specific version range:
+ ///
+ /// SkipTestIfVersionOutsideRange(
+ /// ["SpecificFeatureTest"],
+ /// currentTestCase,
+ /// "15.0.0",
+ /// "16.5.0",
+ /// "Feature only exists in versions 15.0.0 to 16.5.0"
+ /// );
+ ///
+ ///
+ protected static void SkipTestIfVersionOutsideRange(string[] testCases, string currentTestCase, string minimumVersion, string maximumVersion, string? reason = null)
+ {
+ if (testCases.Contains(currentTestCase))
+ {
+ var inRange = IsVersionGreaterOrEqual(minimumVersion) && IsVersionLessOrEqual(maximumVersion);
+ if (!inRange)
+ {
+ var message = reason ?? $"Test case requires AL version between {minimumVersion} and {maximumVersion}.";
+ Assert.Ignore(message);
+ }
+ }
+ }
+
+ #endregion
+
+ #region Test Helper Methods - Whole Test Requirements
+
+ ///
+ /// Skips the current test if the minimum version requirement is not met
+ ///
+ /// The minimum required version (e.g., "15.0.0")
+ /// Optional custom reason for skipping (if null, a default message will be used)
+ ///
+ /// Use at the beginning of a test method to skip if version is too low:
+ ///
+ /// [Test]
+ /// public void TestNewFeature()
+ /// {
+ /// RequireMinimumVersion("15.0.0", "Feature requires obsolete table support");
+ /// // Test code here
+ /// }
+ ///
+ ///
+ protected static void RequireMinimumVersion(string minimumVersion, string? reason = null)
+ {
+ if (!IsVersionGreaterOrEqual(minimumVersion))
+ {
+ var message = reason ?? $"Test requires AL version {minimumVersion} or higher.";
+ Assert.Ignore(message);
+ }
+ }
+
+ ///
+ /// Skips the current test if the version exceeds the maximum version
+ ///
+ /// The maximum supported version (e.g., "16.0.0")
+ /// Optional custom reason for skipping (if null, a default message will be used)
+ ///
+ /// Use at the beginning of a test method to skip if version is too high:
+ ///
+ /// [Test]
+ /// public void TestLegacyFeature()
+ /// {
+ /// RequireMaximumVersion("16.0.0", "Feature was removed in version 16.0.0");
+ /// // Test code here
+ /// }
+ ///
+ ///
+ protected static void RequireMaximumVersion(string maximumVersion, string? reason = null)
+ {
+ if (!IsVersionLessOrEqual(maximumVersion))
+ {
+ var message = reason ?? $"Test requires AL version {maximumVersion} or lower.";
+ Assert.Ignore(message);
+ }
+ }
+
+ ///
+ /// Skips the current test if the version is outside the specified range
+ ///
+ /// The minimum required version (inclusive, e.g., "15.0.0")
+ /// The maximum supported version (inclusive, e.g., "16.5.0")
+ /// Optional custom reason for skipping (if null, a default message will be used)
+ ///
+ /// Use at the beginning of a test method to skip if version is outside range:
+ ///
+ /// [Test]
+ /// public void TestVersionSpecificFeature()
+ /// {
+ /// RequireVersionRange("15.0.0", "16.5.0", "Feature only exists in this version range");
+ /// // Test code here
+ /// }
+ ///
+ ///
+ protected static void RequireVersionRange(string minimumVersion, string maximumVersion, string? reason = null)
+ {
+ var inRange = IsVersionGreaterOrEqual(minimumVersion) && IsVersionLessOrEqual(maximumVersion);
+ if (!inRange)
+ {
+ var message = reason ?? $"Test requires AL version between {minimumVersion} and {maximumVersion}.";
+ Assert.Ignore(message);
+ }
+ }
+
+ ///
+ /// Skips the current test if the Nav.CodeAnalysis version could not be detected
+ ///
+ /// Optional custom reason for skipping (if null, a default message will be used)
+ ///
+ /// Use at the beginning of a test method that absolutely requires version information:
+ ///
+ /// [Test]
+ /// public void TestVersionDependentFeature()
+ /// {
+ /// RequireVersionDetection("This test requires version detection to work properly");
+ /// // Test code here
+ /// }
+ ///
+ ///
+ protected static void RequireVersionDetection(string? reason = null)
+ {
+ if (_navCodeAnalysisVersion == null)
+ {
+ var message = reason ?? "Test requires Nav.CodeAnalysis version detection to be successful.";
+ Assert.Ignore(message);
+ }
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/RoslynTestKit/RoslynTestKit.csproj b/src/RoslynTestKit/RoslynTestKit.csproj
index 69ecd6c..767c537 100644
--- a/src/RoslynTestKit/RoslynTestKit.csproj
+++ b/src/RoslynTestKit/RoslynTestKit.csproj
@@ -17,6 +17,7 @@
+