I have a series of record
types in my C# project that I use with JSON.Net to deserialize json data.
public record RadarrMetadata(
IReadOnlyCollection<string> CustomFormats
);
public record SonarrMetadata(
IReadOnlyCollection<string> ReleaseProfiles
);
public record JsonPaths(
RadarrMetadata Radarr,
SonarrMetadata Sonarr
);
public record RepoMetadata(
JsonPaths JsonPaths
);
I need to "mock" instances of RepoMetadata
to feed it into another object as part of its unit test suite. My solution right now is to create RepoMetadata
objects directly, like so:
[Test, AutoMockData]
public void Directory_separators_are_normalized(
[Frozen] IRepoMetadataParser metadataParser,
RepoPathsFactory sut)
{
var metadata = new RepoMetadata(
new JsonPaths(
new RadarrMetadata(new[] {"foo/bar\\dir"}),
new SonarrMetadata(new[] {""})
)
);
metadataParser.Deserialize().Returns(metadata);
var result = sut.Create();
result.RadarrCustomFormatPaths.Should().NotContain(x => !x.FullName.Contains(_oppositeSlash));
However, I do not like manually constructing RepoMetadata
for these reasons:
- If the surface of this object and its properties changes later, this test will break even though those particular properties have nothing to do with what this test is trying to verify.
- Sort of a continuation of the above point: I have to create and pass in
SonarrMetadata
even though this type is inconsequential to the test.
One way I could solve this is by defining an interface for every record
class, including nested types. Then I could use AutoFixture to create the object and just specify a return value for the properties I care about using NSubsitute:
[Test, AutoMockData]
public void Directory_separators_are_normalized(
[Frozen] IRepoMetadata metadata,
RepoPathsFactory sut)
{
metadata.JsonPaths.Radarr.CustomFormats.Returns(new[] {"foo/bar\\dir"});
var result = sut.Create();
result.RadarrCustomFormatPaths.Should().NotContain(x => !x.FullName.Contains(_oppositeSlash));
}
However, it "feels wrong" to create this many interfaces (one per ORM type). I don't know if I'm on the right track. However, I really love how clean and concise the above alternative is when I introduce interfaces:
- Test is cleaner / more concise
- I don't need
IRepoMetadataParser
(or other intermediate objects) - I don't need to redundantly construct properties that I don't care about in
RepoMetadata
which makes this test less fragile when the data structure changes later to add more fields.
Is my approach with interfaces the best way? Should I go with mutable types instead (add setters to the properties)? The immutable types work great for the actual business logic, but the unit tests are troublesome because of it.