diff --git a/CryptoExchange.Net/Testing/SharedRestRequestValidator.cs b/CryptoExchange.Net/Testing/SharedRestRequestValidator.cs
new file mode 100644
index 00000000..498222a4
--- /dev/null
+++ b/CryptoExchange.Net/Testing/SharedRestRequestValidator.cs
@@ -0,0 +1,126 @@
+using CryptoExchange.Net.Clients;
+using CryptoExchange.Net.Objects;
+using CryptoExchange.Net.SharedApis;
+using CryptoExchange.Net.Testing.Comparers;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CryptoExchange.Net.Testing
+{
+ ///
+ /// Validator for REST requests, comparing path, http method, authentication and response parsing
+ ///
+ /// The Rest client
+ public class SharedRestRequestValidator where TClient : BaseRestClient
+ {
+ private readonly TClient _client;
+ private readonly Func _isAuthenticated;
+ private readonly string _folder;
+ private readonly string _baseAddress;
+ private readonly string? _nestedPropertyForCompare;
+
+ ///
+ /// ctor
+ ///
+ /// Client to test
+ /// Folder for json test values
+ /// The base address that is expected
+ /// Func for checking if the request is authenticated
+ /// Property to use for compare
+ public SharedRestRequestValidator(TClient client, string folder, string baseAddress, Func isAuthenticated, string? nestedPropertyForCompare = null)
+ {
+ _client = client;
+ _folder = folder;
+ _baseAddress = baseAddress;
+ _nestedPropertyForCompare = nestedPropertyForCompare;
+ _isAuthenticated = isAuthenticated;
+ }
+
+ ///
+ /// Validate a request
+ ///
+ /// Expected response type
+ /// Method invocation
+ /// Method name for looking up json test values
+ /// Request options
+ ///
+ ///
+ public Task ValidateAsync(
+ Func>> methodInvoke,
+ string name,
+ EndpointOptions endpointOptions,
+ params Func[] validation)
+ => ValidateAsync(methodInvoke, name, endpointOptions, validation);
+
+ ///
+ /// Validate a request
+ ///
+ /// Expected response type
+ /// The concrete response type
+ /// Method invocation
+ /// Method name for looking up json test values
+ /// Request options
+ ///
+ ///
+ public async Task ValidateAsync(
+ Func>> methodInvoke,
+ string name,
+ EndpointOptions endpointOptions,
+ params Func[] validation) where TActualResponse : TResponse
+ {
+ var listener = new EnumValueTraceListener();
+ Trace.Listeners.Add(listener);
+
+ var path = Directory.GetParent(Environment.CurrentDirectory)!.Parent!.Parent!.FullName;
+ FileStream file;
+ try
+ {
+ file = File.OpenRead(Path.Combine(path, _folder, $"{name}.txt"));
+ }
+ catch (FileNotFoundException)
+ {
+ throw new Exception($"Response file not found for {name}: {path}");
+ }
+
+ var buffer = new byte[file.Length];
+ await file.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
+ file.Close();
+
+ var data = Encoding.UTF8.GetString(buffer);
+ using var reader = new StringReader(data);
+ var expectedMethod = reader.ReadLine();
+ var expectedPath = reader.ReadLine();
+ var expectedAuth = bool.Parse(reader.ReadLine()!);
+ var response = reader.ReadToEnd();
+
+ TestHelpers.ConfigureRestClient(_client, response, System.Net.HttpStatusCode.OK);
+ var result = await methodInvoke(_client).ConfigureAwait(false);
+
+ // Check request/response properties
+ if (result.Error != null)
+ throw new Exception(name + " returned error " + result.Error);
+ if (endpointOptions.NeedsAuthentication != expectedAuth)
+ throw new Exception(name + $" authentication not matched. Expected: {expectedAuth}, Actual: {_isAuthenticated(result.AsDataless())}");
+ if (result.RequestMethod != new HttpMethod(expectedMethod!))
+ throw new Exception(name + $" http method not matched. Expected {expectedMethod}, Actual: {result.RequestMethod}");
+ if (expectedPath != result.RequestUrl!.Replace(_baseAddress, "").Split(new char[] { '?' })[0])
+ throw new Exception(name + $" path not matched. Expected: {expectedPath}, Actual: {result.RequestUrl!.Replace(_baseAddress, "").Split(new char[] { '?' })[0]}");
+
+ var index = 0;
+ foreach(var validate in validation)
+ {
+ if (!validate(result.Data!))
+ throw new Exception(name + $" response validation #{index} failed");
+
+ index++;
+ }
+
+ Trace.Listeners.Remove(listener);
+ }
+ }
+}