machine.specifications icon indicating copy to clipboard operation
machine.specifications copied to clipboard

Nullable Reference Types (C# 8.0) and MSpec Tests

Open alexringeri-xero opened this issue 5 years ago • 2 comments

I am encountering compilation errors when enabling the 'nullable reference types' language feature on an MSpec example. Is there a recommended approach for writing idiomatic MSpec tests with nullable reference types enabled? https://docs.microsoft.com/en-us/dotnet/csharp/nullable-references https://devblogs.microsoft.com/dotnet/embracing-nullable-reference-types/

Example:

[Subject("Authentication")]
class When_authenticating_an_admin_user
{
    static SecurityService subject;
    static UserToken user_token;

    Establish context = () =>
        subject = new SecurityService(foo, bar);

    Because of = () =>
        user_token = subject.Authenticate("username", "password");

    It should_indicate_the_users_role = () =>
        user_token.Role.ShouldEqual(Roles.Admin);

    It should_have_a_unique_session_id = () =>
        user_token.SessionId.ShouldNotBeNull();
}

Error:

Non-nullable field 'subject' is uninitialized. Consider declaring the field as nullable. One approach to resolve the issue is to declare subject and user_token as nullable, and assume they are not null after the Establish step. We can use the null-forgiving operator (!) to assume the reference is not null. This makes the tests harder to read and harder to determine the test intent.

I am looking for a better approach.

Updated Example:

[Subject("Authentication")]
class When_authenticating_an_admin_user
{
    static SecurityService? subject;
    static UserToken? user_token;

    Establish context = () =>
        subject = new SecurityService(foo, bar);

    Because of = () =>
        user_token = subject!.Authenticate("username", "password");

    It should_indicate_the_users_role = () =>
        user_token!.Role.ShouldEqual(Roles.Admin);

    It should_have_a_unique_session_id = () =>
        user_token!.SessionId.ShouldNotBeNull();
}

alexringeri-xero avatar Feb 05 '20 22:02 alexringeri-xero

Bottom line, no we don't have a tried and tested way of doing this yet. I'm in the middle of an extensive refactoring that will address things like async/await support, so I'll add this to the list of things we need to address.

robertcoltheart avatar Feb 09 '20 11:02 robertcoltheart

@alexringeri-xero

An alternative to your updated example is to move the null-forgiving operator to the initialization of the fields. I think this has less of an impact on test readability:

[Subject("Authentication")]
class When_authenticating_an_admin_user
{
    static SecurityService subject = null!;
    static UserToken? user_token = null!;

    Establish context = () =>
        subject = new SecurityService(foo, bar);

    Because of = () =>
        user_token = subject.Authenticate("username", "password");

    It should_indicate_the_users_role = () =>
        user_token.Role.ShouldEqual(Roles.Admin);

    It should_have_a_unique_session_id = () =>
        user_token.SessionId.ShouldNotBeNull();
}

This will prevent the nullable reference types support from detecting bugs like using these fields before you initialize them in setup though (essentially the same as in your updated example).

RedwoodForest avatar Jun 26 '20 21:06 RedwoodForest