Nullable Reference Types (C# 8.0) and MSpec Tests
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();
}
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.
@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).