Skip to content

[Tests] ServiceRepositoryTests — Brittle reflection-based property verification couples tests to DTO shape #736

@Christophe-Rogiers

Description

@Christophe-Rogiers

Severity: Warning
File: tests/Servy.Infrastructure.UnitTests/Data/ServiceRepositoryTests.cs lines 37–127 (pattern repeats)

Description:
The repository tests verify Dapper parameter objects with It.Is<object>(obj => GetPropertyValue(obj, "Password") == "p_enc" && GetPropertyValue(obj, "Parameters") == "args_enc" && ...). GetPropertyValue is a reflection helper that looks up properties by string name. Each assertion chains nine or more string-keyed property reads:

It.Is<object>(obj =>
    GetPropertyValue(obj, "Password") == "p_enc" &&
    GetPropertyValue(obj, "Parameters") == "args_enc" &&
    GetPropertyValue(obj, "EnvironmentVariables") == "env_enc" &&
    GetPropertyValue(obj, "PreLaunchParams") == "pre_args_enc" &&
    // …5+ more …
)

Problems this creates:

  • Any rename in ServiceDto (e.g., PreLaunchParamsPreLaunchArguments) breaks every test even if production behaviour is unchanged.
  • A missing property in the anonymous object silently returns null from the reflection helper, which silently makes the predicate false, which presents as a generic "Verify() expected 1 call, got 0" error without telling the reviewer that the real problem is a name mismatch.
  • The test couples to the string shape of the encrypted-DTO wire format rather than the contract — "this call encrypts Password, Parameters and envVars before persisting".

This is related to #552 (mock-only tests verify Moq dispatch) but is a specifically worse variant: it is both mock-only and stringly-typed.

Suggested fix:
Replace the property-by-property reflection predicate with a typed capture + comparer:

ServiceDto? captured = null;
_dapperMock.Setup(d => d.ExecuteAsync(It.IsAny<string>(),
        Capture.In(captured, x => x as ServiceDto), // or use Moq's Capture.With
        It.IsAny<CancellationToken>()))
    .ReturnsAsync(1);

// act …

Assert.NotNull(captured);
Assert.Equal("p_enc", captured.Password);
Assert.Equal("args_enc", captured.Parameters);
// … etc

If Dapper is being called with an anonymous type (no ServiceDto), consider encapsulating the encryption step in a seam that does return a typed object, and test that seam directly. That way the repository test can focus on "did we ask the seam to encrypt this DTO?" rather than "what fields did the anonymous parameter bag end up with?".

Metadata

Metadata

Assignees

Labels

testsChanges to test code and coverage

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions