diff --git a/Directory.Build.props b/Directory.Build.props
index 0550a44..45adbbb 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -68,9 +68,9 @@ SOFTWARE. -->
$(MSBuildThisFileDirectory)\
Debug
$(Configuration)
- true
- true
- false
+ true
+ true
+ false
enable
true
communication
diff --git a/src/JSSoft.Communication/Converters/ArgumentExceptionConverter.cs b/src/JSSoft.Communication/Converters/ArgumentExceptionConverter.cs
new file mode 100644
index 0000000..ff60483
--- /dev/null
+++ b/src/JSSoft.Communication/Converters/ArgumentExceptionConverter.cs
@@ -0,0 +1,33 @@
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
+
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace JSSoft.Communication.Converters;
+
+internal sealed class ArgumentExceptionConverter : JsonConverter
+{
+ public const string ParamName = "paramName";
+ public const string Message = "message";
+
+ public override ArgumentException? Read(
+ ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ var props = reader.ReadObject(capacity: 2);
+ var message = props[Message] ?? throw new JsonException($"{Message} is not found");
+ return new ArgumentException(message);
+ }
+
+ public override void Write(
+ Utf8JsonWriter writer, ArgumentException value, JsonSerializerOptions options)
+ {
+ writer.WriteStartObject();
+ writer.WriteString(Message, value.Message);
+ writer.WriteString(ParamName, value.ParamName);
+ writer.WriteEndObject();
+ }
+}
diff --git a/src/JSSoft.Communication/Converters/ArgumentNullExceptionConverter.cs b/src/JSSoft.Communication/Converters/ArgumentNullExceptionConverter.cs
new file mode 100644
index 0000000..b350b1a
--- /dev/null
+++ b/src/JSSoft.Communication/Converters/ArgumentNullExceptionConverter.cs
@@ -0,0 +1,33 @@
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
+
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace JSSoft.Communication.Converters;
+
+internal sealed class ArgumentNullExceptionConverter : JsonConverter
+{
+ public const string ParamName = "paramName";
+ public const string Message = "message";
+
+ public override ArgumentNullException? Read(
+ ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ var props = reader.ReadObject(capacity: 2);
+ var message = props[Message] ?? throw new JsonException($"{Message} is not found");
+ return new ArgumentNullException(message);
+ }
+
+ public override void Write(
+ Utf8JsonWriter writer, ArgumentNullException value, JsonSerializerOptions options)
+ {
+ writer.WriteStartObject();
+ writer.WriteString(ParamName, value.ParamName);
+ writer.WriteString(Message, value.Message);
+ writer.WriteEndObject();
+ }
+}
diff --git a/src/JSSoft.Communication/Converters/ArgumentOutOfRangeExceptionConverter.cs b/src/JSSoft.Communication/Converters/ArgumentOutOfRangeExceptionConverter.cs
new file mode 100644
index 0000000..c971803
--- /dev/null
+++ b/src/JSSoft.Communication/Converters/ArgumentOutOfRangeExceptionConverter.cs
@@ -0,0 +1,34 @@
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
+
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace JSSoft.Communication.Converters;
+
+internal sealed class ArgumentOutOfRangeExceptionConverter
+ : JsonConverter
+{
+ public const string ParamName = "paramName";
+ public const string Message = "message";
+
+ public override ArgumentOutOfRangeException? Read(
+ ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ var props = reader.ReadObject(capacity: 2);
+ var message = props[Message] ?? throw new JsonException($"{Message} is not found");
+ return new ArgumentOutOfRangeException(message);
+ }
+
+ public override void Write(
+ Utf8JsonWriter writer, ArgumentOutOfRangeException value, JsonSerializerOptions options)
+ {
+ writer.WriteStartObject();
+ writer.WriteString(ParamName, value.ParamName);
+ writer.WriteString(Message, value.Message);
+ writer.WriteEndObject();
+ }
+}
diff --git a/src/JSSoft.Communication/Converters/ConverterExtensions.cs b/src/JSSoft.Communication/Converters/ConverterExtensions.cs
new file mode 100644
index 0000000..a1b6822
--- /dev/null
+++ b/src/JSSoft.Communication/Converters/ConverterExtensions.cs
@@ -0,0 +1,43 @@
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
+
+using System.Collections.Generic;
+using System.Text.Json;
+
+namespace JSSoft.Communication.Converters;
+
+internal static class ConverterExtensions
+{
+ public static IDictionary ReadObject(
+ this ref Utf8JsonReader @this, int capacity)
+ {
+ var props = new Dictionary(capacity);
+ if (@this.TokenType != JsonTokenType.StartObject)
+ {
+ throw new JsonException("Invalid Json format");
+ }
+
+ while (@this.Read())
+ {
+ if (@this.TokenType == JsonTokenType.EndObject)
+ {
+ @this.Read();
+ return props;
+ }
+
+ if (@this.TokenType != JsonTokenType.PropertyName)
+ {
+ throw new JsonException("Invalid Json format");
+ }
+
+ var key = @this.GetString() ?? throw new JsonException("Invalid Json format");
+ @this.Read();
+ var value = @this.GetString();
+ props.Add(key, value);
+ }
+
+ throw new JsonException("Invalid Json format");
+ }
+}
diff --git a/src/JSSoft.Communication/Converters/ExceptionConverter.cs b/src/JSSoft.Communication/Converters/ExceptionConverter.cs
new file mode 100644
index 0000000..ff5f19a
--- /dev/null
+++ b/src/JSSoft.Communication/Converters/ExceptionConverter.cs
@@ -0,0 +1,21 @@
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
+
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace JSSoft.Communication.Converters;
+
+internal sealed class ExceptionConverter : JsonConverter
+{
+ public override Exception? Read(
+ ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ => reader.GetString() is string message ? new Exception(message) : null;
+
+ public override void Write(
+ Utf8JsonWriter writer, Exception value, JsonSerializerOptions options)
+ => writer.WriteStringValue(value.Message);
+}
diff --git a/src/JSSoft.Communication/Converters/ExceptionConverterFactory.cs b/src/JSSoft.Communication/Converters/ExceptionConverterFactory.cs
new file mode 100644
index 0000000..b2db278
--- /dev/null
+++ b/src/JSSoft.Communication/Converters/ExceptionConverterFactory.cs
@@ -0,0 +1,46 @@
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace JSSoft.Communication.Converters;
+
+internal sealed class ExceptionConverterFactory : JsonConverterFactory
+{
+ private readonly Dictionary _converterByType = [];
+
+ public override bool CanConvert(Type typeToConvert)
+ => typeof(Exception).IsAssignableFrom(typeToConvert);
+
+ public override JsonConverter? CreateConverter(
+ Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (_converterByType.TryGetValue(typeToConvert, out var converter) != true)
+ {
+ var genericType = typeof(InternalJsonConverter<>).MakeGenericType(typeToConvert);
+ converter = (JsonConverter)Activator.CreateInstance(genericType)!;
+ _converterByType.Add(typeToConvert, converter);
+ }
+
+ return converter;
+ }
+
+ private sealed class InternalJsonConverter : JsonConverter
+ where T : Exception
+ {
+ public override T? Read(
+ ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ => reader.GetString() is string message
+ ? (T)Activator.CreateInstance(typeToConvert, message)!
+ : null;
+
+ public override void Write(
+ Utf8JsonWriter writer, T value, JsonSerializerOptions options)
+ => writer.WriteStringValue(value.Message);
+ }
+}
diff --git a/src/JSSoft.Communication/Converters/IndexOutOfRangeExceptionConverter.cs b/src/JSSoft.Communication/Converters/IndexOutOfRangeExceptionConverter.cs
new file mode 100644
index 0000000..f5c201f
--- /dev/null
+++ b/src/JSSoft.Communication/Converters/IndexOutOfRangeExceptionConverter.cs
@@ -0,0 +1,21 @@
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
+
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace JSSoft.Communication.Converters;
+
+internal sealed class IndexOutOfRangeExceptionConverter : JsonConverter
+{
+ public override IndexOutOfRangeException? Read(
+ ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ => reader.GetString() is string message ? new IndexOutOfRangeException(message) : null;
+
+ public override void Write(
+ Utf8JsonWriter writer, IndexOutOfRangeException value, JsonSerializerOptions options)
+ => writer.WriteStringValue(value.Message);
+}
diff --git a/src/JSSoft.Communication/Converters/InvalidOperationExceptionConverter.cs b/src/JSSoft.Communication/Converters/InvalidOperationExceptionConverter.cs
new file mode 100644
index 0000000..37489dd
--- /dev/null
+++ b/src/JSSoft.Communication/Converters/InvalidOperationExceptionConverter.cs
@@ -0,0 +1,21 @@
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
+
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace JSSoft.Communication.Converters;
+
+internal sealed class InvalidOperationExceptionConverter : JsonConverter
+{
+ public override InvalidOperationException? Read(
+ ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ => reader.GetString() is string message ? new InvalidOperationException(message) : null;
+
+ public override void Write(
+ Utf8JsonWriter writer, InvalidOperationException value, JsonSerializerOptions options)
+ => writer.WriteStringValue(value.Message);
+}
diff --git a/src/JSSoft.Communication/Converters/NotSupportedExceptionConverter.cs b/src/JSSoft.Communication/Converters/NotSupportedExceptionConverter.cs
new file mode 100644
index 0000000..dcd2742
--- /dev/null
+++ b/src/JSSoft.Communication/Converters/NotSupportedExceptionConverter.cs
@@ -0,0 +1,21 @@
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
+
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace JSSoft.Communication.Converters;
+
+internal sealed class NotSupportedExceptionConverter : JsonConverter
+{
+ public override NotSupportedException? Read(
+ ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ => reader.GetString() is string message ? new NotSupportedException(message) : null;
+
+ public override void Write(
+ Utf8JsonWriter writer, NotSupportedException value, JsonSerializerOptions options)
+ => writer.WriteStringValue(value.Message);
+}
diff --git a/src/JSSoft.Communication/Converters/NullReferenceExceptionConverter.cs b/src/JSSoft.Communication/Converters/NullReferenceExceptionConverter.cs
new file mode 100644
index 0000000..c1ad23b
--- /dev/null
+++ b/src/JSSoft.Communication/Converters/NullReferenceExceptionConverter.cs
@@ -0,0 +1,21 @@
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
+
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace JSSoft.Communication.Converters;
+
+internal sealed class NullReferenceExceptionConverter : JsonConverter
+{
+ public override NullReferenceException? Read(
+ ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ => reader.GetString() is string message ? new NullReferenceException(message) : null;
+
+ public override void Write(
+ Utf8JsonWriter writer, NullReferenceException value, JsonSerializerOptions options)
+ => writer.WriteStringValue(value.Message);
+}
diff --git a/src/JSSoft.Communication/Converters/ObjectDisposedExceptionConverter.cs b/src/JSSoft.Communication/Converters/ObjectDisposedExceptionConverter.cs
new file mode 100644
index 0000000..e420a4f
--- /dev/null
+++ b/src/JSSoft.Communication/Converters/ObjectDisposedExceptionConverter.cs
@@ -0,0 +1,34 @@
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
+
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace JSSoft.Communication.Converters;
+
+internal sealed class ObjectDisposedExceptionConverter : JsonConverter
+{
+ public const string ObjectName = "objectName";
+ public const string Message = "message";
+
+ public override ObjectDisposedException? Read(
+ ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ var props = reader.ReadObject(capacity: 2);
+ var objectName = props[ObjectName] ?? throw new JsonException($"{ObjectName} is not found");
+ var message = props[Message] ?? throw new JsonException($"{Message} is not found");
+ return new ObjectDisposedException(objectName, message);
+ }
+
+ public override void Write(
+ Utf8JsonWriter writer, ObjectDisposedException value, JsonSerializerOptions options)
+ {
+ writer.WriteStartObject();
+ writer.WriteString(ObjectName, value.ObjectName);
+ writer.WriteString(Message, value.Message);
+ writer.WriteEndObject();
+ }
+}
diff --git a/src/JSSoft.Communication/Converters/SystemExceptionConverter.cs b/src/JSSoft.Communication/Converters/SystemExceptionConverter.cs
new file mode 100644
index 0000000..b3b67ff
--- /dev/null
+++ b/src/JSSoft.Communication/Converters/SystemExceptionConverter.cs
@@ -0,0 +1,21 @@
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
+
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace JSSoft.Communication.Converters;
+
+internal sealed class SystemExceptionConverter : JsonConverter
+{
+ public override SystemException? Read(
+ ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ => reader.GetString() is string message ? new SystemException(message) : null;
+
+ public override void Write(
+ Utf8JsonWriter writer, SystemException value, JsonSerializerOptions options)
+ => writer.WriteStringValue(value.Message);
+}
diff --git a/src/JSSoft.Communication/DefaultSerializer.cs b/src/JSSoft.Communication/DefaultSerializer.cs
new file mode 100644
index 0000000..9fe955d
--- /dev/null
+++ b/src/JSSoft.Communication/DefaultSerializer.cs
@@ -0,0 +1,38 @@
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
+
+using System;
+using System.Text.Json;
+using JSSoft.Communication.Converters;
+
+namespace JSSoft.Communication;
+
+internal sealed class DefaultSerializer : ISerializer
+{
+ private static readonly JsonSerializerOptions Options = new()
+ {
+ IncludeFields = true,
+ Converters =
+ {
+ new ArgumentExceptionConverter(),
+ new ArgumentNullExceptionConverter(),
+ new ArgumentOutOfRangeExceptionConverter(),
+ new ExceptionConverter(),
+ new IndexOutOfRangeExceptionConverter(),
+ new InvalidOperationExceptionConverter(),
+ new ObjectDisposedExceptionConverter(),
+ new NotSupportedExceptionConverter(),
+ new NullReferenceExceptionConverter(),
+ new SystemExceptionConverter(),
+ new ExceptionConverterFactory(),
+ },
+ };
+
+ public string Serialize(Type type, object? data)
+ => JsonSerializer.Serialize(data, type, Options);
+
+ public object? Deserialize(Type type, string text)
+ => JsonSerializer.Deserialize(text, type, Options);
+}
diff --git a/src/JSSoft.Communication/JsonSerializerProvider.cs b/src/JSSoft.Communication/DefaultSerializerProvider.cs
similarity index 59%
rename from src/JSSoft.Communication/JsonSerializerProvider.cs
rename to src/JSSoft.Communication/DefaultSerializerProvider.cs
index fadb296..5d12e66 100644
--- a/src/JSSoft.Communication/JsonSerializerProvider.cs
+++ b/src/JSSoft.Communication/DefaultSerializerProvider.cs
@@ -1,18 +1,18 @@
-//
+//
// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
// Licensed under the MIT License. See LICENSE.md in the project root for license information.
//
namespace JSSoft.Communication;
-internal sealed class JsonSerializerProvider : ISerializerProvider
+internal sealed class DefaultSerializerProvider : ISerializerProvider
{
public const string DefaultName = "json";
- public static readonly JsonSerializerProvider Default = new();
+ public static readonly DefaultSerializerProvider Default = new();
public string Name => DefaultName;
public ISerializer Create(IServiceContext serviceContext)
- => new JsonSerializer();
+ => new DefaultSerializer();
}
diff --git a/src/JSSoft.Communication/Grpc/AdaptorClient.cs b/src/JSSoft.Communication/Grpc/AdaptorClient.cs
index 9de71e1..5270a58 100644
--- a/src/JSSoft.Communication/Grpc/AdaptorClient.cs
+++ b/src/JSSoft.Communication/Grpc/AdaptorClient.cs
@@ -12,7 +12,6 @@
using Grpc.Core;
using JSSoft.Communication.Extensions;
using JSSoft.Communication.Logging;
-using Newtonsoft.Json;
#if NETSTANDARD
using GrpcChannel = Grpc.Core.Channel;
@@ -448,7 +447,7 @@ private void ThrowException(Type exceptionType, string data)
throw new InvalidOperationException("serializer is not set.");
}
- if (JsonConvert.DeserializeObject(data, exceptionType) is Exception exception)
+ if (_serializer.Deserialize(exceptionType, data) is Exception exception)
{
throw exception;
}
diff --git a/src/JSSoft.Communication/Grpc/AdaptorServer.cs b/src/JSSoft.Communication/Grpc/AdaptorServer.cs
index 67a3143..168d5a7 100644
--- a/src/JSSoft.Communication/Grpc/AdaptorServer.cs
+++ b/src/JSSoft.Communication/Grpc/AdaptorServer.cs
@@ -146,7 +146,7 @@ public async Task InvokeAsync(InvokeRequest request, ServerCallCont
var reply = new InvokeReply()
{
ID = string.Empty,
- Data = _serializer.Serialize(typeof(void), null),
+ Data = string.Empty,
};
var methodShortName = methodDescriptor.ShortName;
@@ -160,7 +160,9 @@ public async Task InvokeAsync(InvokeRequest request, ServerCallCont
var reply = new InvokeReply()
{
ID = result.AssemblyQualifiedName,
- Data = _serializer.Serialize(result.ValueType, result.Value),
+ Data = result.ValueType == typeof(void)
+ ? string.Empty
+ : _serializer.Serialize(result.ValueType, result.Value),
};
_serviceContext.Debug($"{id} Invoke: {methodDescriptor.Name}");
diff --git a/src/JSSoft.Communication/JSSoft.Communication.csproj b/src/JSSoft.Communication/JSSoft.Communication.csproj
index 8f70487..3947925 100644
--- a/src/JSSoft.Communication/JSSoft.Communication.csproj
+++ b/src/JSSoft.Communication/JSSoft.Communication.csproj
@@ -35,7 +35,6 @@ SOFTWARE. -->
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
\ No newline at end of file
diff --git a/src/JSSoft.Communication/JsonSerializer.cs b/src/JSSoft.Communication/JsonSerializer.cs
deleted file mode 100644
index 86b66b8..0000000
--- a/src/JSSoft.Communication/JsonSerializer.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-//
-// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
-// Licensed under the MIT License. See LICENSE.md in the project root for license information.
-//
-
-using System;
-using Newtonsoft.Json;
-
-namespace JSSoft.Communication;
-
-internal sealed class JsonSerializer : ISerializer
-{
- private static readonly JsonSerializerSettings Settings = new();
-
- public string Serialize(Type type, object? data)
- => JsonConvert.SerializeObject(data, type, Settings);
-
- public object? Deserialize(Type type, string text)
- => JsonConvert.DeserializeObject(text, type, Settings);
-}
diff --git a/src/JSSoft.Communication/ServiceContextBase.cs b/src/JSSoft.Communication/ServiceContextBase.cs
index 90f36f4..465edd1 100644
--- a/src/JSSoft.Communication/ServiceContextBase.cs
+++ b/src/JSSoft.Communication/ServiceContextBase.cs
@@ -51,7 +51,7 @@ protected ServiceContextBase(IService[] services)
public virtual IAdaptorProvider AdaptorProvider => Communication.AdaptorProvider.Default;
- public virtual ISerializerProvider SerializerProvider => JsonSerializerProvider.Default;
+ public virtual ISerializerProvider SerializerProvider => DefaultSerializerProvider.Default;
public ServiceCollection Services { get; }
diff --git a/test/JSSoft.Communication.Tests/Exceptions/SystemExceptionTest.cs b/test/JSSoft.Communication.Tests/Exceptions/SystemExceptionTest.cs
new file mode 100644
index 0000000..a76ec85
--- /dev/null
+++ b/test/JSSoft.Communication.Tests/Exceptions/SystemExceptionTest.cs
@@ -0,0 +1,13 @@
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
+
+using Xunit.Abstractions;
+
+namespace JSSoft.Communication.Tests.Exceptions;
+
+public sealed class SystemExceptionTest(ITestOutputHelper logger)
+ : ExceptionTestBase(logger)
+{
+}
diff --git a/test/JSSoft.Communication.Tests/Exceptions/TestException.cs b/test/JSSoft.Communication.Tests/Exceptions/TestException.cs
new file mode 100644
index 0000000..0faab9c
--- /dev/null
+++ b/test/JSSoft.Communication.Tests/Exceptions/TestException.cs
@@ -0,0 +1,18 @@
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
+
+namespace JSSoft.Communication.Tests.Exceptions;
+
+public sealed class TestException : Exception
+{
+ public TestException()
+ {
+ }
+
+ public TestException(string message)
+ : base(message)
+ {
+ }
+}
diff --git a/test/JSSoft.Communication.Tests/Exceptions/TestExceptionTest.cs b/test/JSSoft.Communication.Tests/Exceptions/TestExceptionTest.cs
new file mode 100644
index 0000000..d32e1ea
--- /dev/null
+++ b/test/JSSoft.Communication.Tests/Exceptions/TestExceptionTest.cs
@@ -0,0 +1,13 @@
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
+
+using Xunit.Abstractions;
+
+namespace JSSoft.Communication.Tests.Exceptions;
+
+public sealed class TestExceptionTest(ITestOutputHelper logger)
+ : ExceptionTestBase(logger)
+{
+}