Skip to content

Commit 05585c9

Browse files
authored
Code Quality: Write tests (#10)
1 parent a7f0e5d commit 05585c9

File tree

8 files changed

+243
-1
lines changed

8 files changed

+243
-1
lines changed

JsonBinder.sln

+26
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,20 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Riverside.JsonBinder", "src
77
EndProject
88
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Riverside.JsonBinder.Console", "src\Riverside.JsonBinder.Console\Riverside.JsonBinder.Console.csproj", "{A0C21F10-32C7-4D6D-A8F1-022C808A4615}"
99
EndProject
10+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
11+
EndProject
12+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{FDCA6BBE-B3FF-4CF8-AC2F-AEA0F5A2171E}"
13+
EndProject
14+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "eng", "eng", "{A9D4D6A1-C565-435B-8CE2-36FDF6D163F8}"
15+
ProjectSection(SolutionItems) = preProject
16+
eng\AdditionalFiles.props = eng\AdditionalFiles.props
17+
eng\CurrentVersion.props = eng\CurrentVersion.props
18+
eng\PackageLogo.png = eng\PackageLogo.png
19+
eng\PackageMetadata.props = eng\PackageMetadata.props
20+
EndProjectSection
21+
EndProject
22+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Riverside.JsonBinder.Tests", "tests\Riverside.JsonBinder.Tests\Riverside.JsonBinder.Tests.csproj", "{4197DF7D-233B-45E6-911F-485B188A6B79}"
23+
EndProject
1024
Global
1125
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1226
Debug|Any CPU = Debug|Any CPU
@@ -21,8 +35,20 @@ Global
2135
{A0C21F10-32C7-4D6D-A8F1-022C808A4615}.Debug|Any CPU.Build.0 = Debug|Any CPU
2236
{A0C21F10-32C7-4D6D-A8F1-022C808A4615}.Release|Any CPU.ActiveCfg = Release|Any CPU
2337
{A0C21F10-32C7-4D6D-A8F1-022C808A4615}.Release|Any CPU.Build.0 = Release|Any CPU
38+
{4197DF7D-233B-45E6-911F-485B188A6B79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39+
{4197DF7D-233B-45E6-911F-485B188A6B79}.Debug|Any CPU.Build.0 = Debug|Any CPU
40+
{4197DF7D-233B-45E6-911F-485B188A6B79}.Release|Any CPU.ActiveCfg = Release|Any CPU
41+
{4197DF7D-233B-45E6-911F-485B188A6B79}.Release|Any CPU.Build.0 = Release|Any CPU
2442
EndGlobalSection
2543
GlobalSection(SolutionProperties) = preSolution
2644
HideSolutionNode = FALSE
2745
EndGlobalSection
46+
GlobalSection(NestedProjects) = preSolution
47+
{0B9E827E-F59A-419F-9BAB-DBB5512627D9} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
48+
{A0C21F10-32C7-4D6D-A8F1-022C808A4615} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
49+
{4197DF7D-233B-45E6-911F-485B188A6B79} = {FDCA6BBE-B3FF-4CF8-AC2F-AEA0F5A2171E}
50+
EndGlobalSection
51+
GlobalSection(ExtensibilityGlobals) = postSolution
52+
SolutionGuid = {A7D91CF4-FBEE-4762-A037-CFA995F795FC}
53+
EndGlobalSection
2854
EndGlobal
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
using System.Runtime.CompilerServices;
2+
3+
[assembly: InternalsVisibleTo("Riverside.JsonBinder.Tests")]

src/Riverside.JsonBinder/Serialization/PHPSerializer.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ private void ProcessNode(JsonNode node, string className, List<string> classes)
3030
{
3131
if (node is JsonObject obj)
3232
{
33-
var classDef = $"class {className} {{\n";
33+
var classDef = $"class {className} {{";
3434
foreach (var property in obj)
3535
{
3636
classDef += $"\n public ${property.Key};";

src/Riverside.JsonBinder/Serialization/RubySerializer.cs

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ private void ProcessNode(JsonNode node, string className, List<string> classes)
3232
{
3333
var classDef = $"class {className}\n attr_accessor ";
3434
classDef += string.Join(", ", obj.Select(property => $":{property.Key}"));
35+
classDef += "\nend";
3536
classes.Add(classDef);
3637

3738
foreach (var property in obj)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
namespace Riverside.JsonBinder.Tests;
2+
3+
[TestClass]
4+
public class JsonSerializerTests
5+
{
6+
[TestMethod]
7+
[DataRow("{\"name\":\"John\"}", SerializableLanguage.CSharp, "public class Root\n{\n public string name { get; set; }\n}")]
8+
[DataRow("{\"name\":\"John\"}", SerializableLanguage.Python, "class Root:\n def __init__(self):\n self.name: str = None")]
9+
[DataRow("{\"name\":\"John\"}", SerializableLanguage.Java, "public class Root {\n private String name;\n public String getname() { return name; }\n public void setname(String name) { this.name = name; }\n}")]
10+
public void ConvertTo_ValidJson_ReturnsExpectedResult(string json, SerializableLanguage language, string expected)
11+
{
12+
// Act
13+
var result = JsonSerializer.ConvertTo(json, language);
14+
15+
// Assert
16+
Assert.AreEqual(Normalize(expected), Normalize(result));
17+
}
18+
19+
[TestMethod]
20+
public void ConvertTo_InvalidJson_ThrowsException()
21+
{
22+
// Arrange
23+
string invalidJson = "invalid json";
24+
25+
// Act
26+
try
27+
{
28+
// Stupidly, JsonReaderException is internal, so it can't be caught.
29+
// Instead, we need to compare the exception message with the expected message.
30+
JsonSerializer.ConvertTo(invalidJson, SerializableLanguage.CSharp);
31+
}
32+
catch (Exception ex)
33+
{
34+
// Assert
35+
Assert.AreEqual("'i' is an invalid start of a value. LineNumber: 0 | BytePositionInLine: 0.", ex.Message);
36+
}
37+
}
38+
39+
[TestMethod]
40+
[ExpectedException(typeof(ArgumentNullException))]
41+
public void ConvertTo_NullJson_ThrowsException()
42+
{
43+
// Arrange
44+
string? invalidJson = null;
45+
46+
JsonSerializer.ConvertTo(invalidJson, SerializableLanguage.CSharp);
47+
}
48+
49+
[TestMethod]
50+
[ExpectedException(typeof(NotSupportedException))]
51+
public void ConvertTo_UnsupportedLanguage_ThrowsNotSupportedException()
52+
{
53+
// Arrange
54+
string json = "{\"name\":\"John\"}";
55+
56+
// Act
57+
JsonSerializer.ConvertTo(json, (SerializableLanguage)0xFFFFFF);
58+
}
59+
60+
[TestMethod]
61+
public void ConvertTo_JsonArray_WrapsInRootObject()
62+
{
63+
// Arrange
64+
string jsonArray = "[{\"name\":\"John\"}]";
65+
string expected = """
66+
public class Root
67+
{
68+
public List<Items> Items { get; set; }
69+
}
70+
71+
public class ItemsItem
72+
{
73+
public string name { get; set; }
74+
}
75+
76+
public class Items
77+
{
78+
public List<ItemsItem> Items { get; set; } = new List<ItemsItem>();
79+
}
80+
""";
81+
// Act
82+
var result = JsonSerializer.ConvertTo(jsonArray, SerializableLanguage.CSharp);
83+
84+
// Assert
85+
Assert.AreEqual(Normalize(expected), Normalize(result));
86+
}
87+
88+
private static string Normalize(string input)
89+
=> input.Replace("\r\n", "\n").Trim();
90+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
using System.Text.Json.Nodes;
2+
using Riverside.JsonBinder.Serialization;
3+
4+
namespace Riverside.JsonBinder.Tests;
5+
6+
[TestClass]
7+
public class LanguageSerializerTests
8+
{
9+
private static readonly Dictionary<SerializableLanguage, Type> SerializerTypes = new()
10+
{
11+
{ SerializableLanguage.CSharp, typeof(CSharpSerializer) },
12+
{ SerializableLanguage.Python, typeof(PythonSerializer) },
13+
{ SerializableLanguage.Java, typeof(JavaSerializer) },
14+
{ SerializableLanguage.JavaScript, typeof(JavaScriptSerializer) },
15+
{ SerializableLanguage.TypeScript, typeof(TypeScriptSerializer) },
16+
{ SerializableLanguage.PHP, typeof(PHPSerializer) },
17+
{ SerializableLanguage.Ruby, typeof(RubySerializer) },
18+
{ SerializableLanguage.Swift, typeof(SwiftSerializer) }
19+
};
20+
21+
[TestMethod]
22+
[DynamicData(nameof(GetTestData), DynamicDataSourceType.Method)]
23+
public void GenerateClasses_ValidJson_ReturnsExpectedResult(SerializableLanguage language, string json, string expected)
24+
{
25+
// Arrange
26+
if (!SerializerTypes.TryGetValue(language, out var serializerType) || serializerType == null)
27+
{
28+
Assert.Fail($"Serializer for language {language} not found.");
29+
return;
30+
}
31+
32+
var serializer = (LanguageSerializer?)Activator.CreateInstance(serializerType);
33+
Assert.IsNotNull(serializer, $"Failed to create instance of {serializerType}");
34+
35+
// Act
36+
var jsonNode = JsonNode.Parse(json);
37+
Assert.IsNotNull(jsonNode, "Failed to parse JSON");
38+
39+
var result = serializer.GenerateClasses(jsonNode, "Root");
40+
41+
// Assert
42+
Assert.AreEqual(expected.Normalize(), result.Normalize());
43+
}
44+
45+
[TestMethod]
46+
[DynamicData(nameof(GetInvalidJsonTestData), DynamicDataSourceType.Method)]
47+
public void GenerateClasses_InvalidJson_ThrowsException(SerializableLanguage language, string invalidJson)
48+
{
49+
// Arrange
50+
if (!SerializerTypes.TryGetValue(language, out var serializerType) || serializerType == null)
51+
{
52+
Assert.Fail($"Serializer for language {language} not found.");
53+
return;
54+
}
55+
56+
var serializer = (LanguageSerializer?)Activator.CreateInstance(serializerType);
57+
Assert.IsNotNull(serializer, $"Failed to create instance of {serializerType}");
58+
59+
// Act
60+
try
61+
{
62+
var jsonNode = JsonNode.Parse(invalidJson);
63+
Assert.IsNotNull(jsonNode, "Failed to parse JSON");
64+
65+
serializer.GenerateClasses(jsonNode, "Root");
66+
}
67+
catch (Exception ex)
68+
{
69+
// Assert
70+
Assert.AreEqual("'i' is an invalid start of a value. LineNumber: 0 | BytePositionInLine: 0.", ex.Message);
71+
}
72+
}
73+
74+
public static IEnumerable<object[]> GetTestData()
75+
{
76+
yield return new object[] { SerializableLanguage.CSharp, "{\"name\":\"John\"}", "public class Root\n{\n public string name { get; set; }\n}" };
77+
yield return new object[] { SerializableLanguage.Python, "{\"name\":\"John\"}", "class Root:\n def __init__(self):\n self.name: str = None" };
78+
yield return new object[] { SerializableLanguage.Java, "{\"name\":\"John\"}", "public class Root {\n private String name;\n public String getname() { return name; }\n public void setname(String name) { this.name = name; }\n}" };
79+
yield return new object[] { SerializableLanguage.JavaScript, "{\"name\":\"John\"}", "class Root {\n constructor() {\n this.name = null;\n }\n}" };
80+
yield return new object[] { SerializableLanguage.TypeScript, "{\"name\":\"John\"}", "class Root {\n constructor() {\n this.name = null;\n }\n}" };
81+
yield return new object[] { SerializableLanguage.PHP, "{\"name\":\"John\"}", "class Root {\n public $name;\n}" };
82+
yield return new object[] { SerializableLanguage.Ruby, "{\"name\":\"John\"}", "class Root\n attr_accessor :name\nend" };
83+
yield return new object[] { SerializableLanguage.Swift, "{\"name\":\"John\"}", "struct Root {\n var name: String?\n}" };
84+
}
85+
86+
public static IEnumerable<object[]> GetInvalidJsonTestData()
87+
{
88+
yield return new object[] { SerializableLanguage.CSharp, "invalid json" };
89+
yield return new object[] { SerializableLanguage.Python, "invalid json" };
90+
yield return new object[] { SerializableLanguage.Java, "invalid json" };
91+
yield return new object[] { SerializableLanguage.JavaScript, "invalid json" };
92+
yield return new object[] { SerializableLanguage.TypeScript, "invalid json" };
93+
yield return new object[] { SerializableLanguage.PHP, "invalid json" };
94+
yield return new object[] { SerializableLanguage.Ruby, "invalid json" };
95+
yield return new object[] { SerializableLanguage.Swift, "invalid json" };
96+
}
97+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<Project Sdk="MSTest.Sdk/3.6.4">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net9.0</TargetFramework>
5+
<LangVersion>latest</LangVersion>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<!--
9+
Displays error on console in addition to the log file. Note that this feature comes with a performance impact.
10+
For more information, visit https://learn.microsoft.com/dotnet/core/testing/unit-testing-platform-integration-dotnet-test#show-failure-per-test
11+
-->
12+
<TestingPlatformShowTestsFailure>true</TestingPlatformShowTestsFailure>
13+
</PropertyGroup>
14+
15+
<PropertyGroup>
16+
<NoWarn>1701;1702;CA1707;CS1591</NoWarn>
17+
<TreatWarningsAsErrors>False</TreatWarningsAsErrors>
18+
</PropertyGroup>
19+
20+
<ItemGroup>
21+
<ProjectReference Include="..\..\src\Riverside.JsonBinder\Riverside.JsonBinder.csproj" />
22+
</ItemGroup>
23+
24+
</Project>

0 commit comments

Comments
 (0)