Having read the quick start at nspec.org, I realized that NSpec might be a useful tool in a scenario which was becoming a bit cumbersome with NUnit alone.
I'm adding an OAuth (or, DotNetOpenAuth) to a website and quickly made a mess of writing test methods such as
[Test]
public void UserIsLoggedInLocallyPriorToInvokingExternalLoginAndExternalLoginSucceedsAndExternalProviderIdIsNotAlreadyAssociatedWithUserAccount()
{
...
}
... and I wound up with maybe a dozen permutations of this theme, for the user already being logged in locally and not locally, the external login succeeding or failing, etc. Not only were the method names unwieldy, but every test needed a setup that contained parts in common with a different set of other tests.
I realized that NSpec's incremental setup capabilities would work great for this, and for a while I was trucking a long wonderfully, with code like
act = () => { actionResult = controller.ExternalLoginCallback(returnUrl); };
context["The user is already logged in"] = () =>
{
before = () => identity.Setup(x => x.IsAuthenticated).Returns(true);
context["The external login succeeds"] = () =>
{
before = () => oauth.Setup(x => x.VerifyAuthentication(It.IsAny<string>())).Returns(new AuthenticationResult(true, providerName, "provideruserid", "username", new Dictionary<string, string>()));
context["External login already exists for current user"] = () =>
{
before = () => authService.Setup(x => x.ExternalLoginExistsForUser(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Returns(true);
it["Should add 'login sucessful' alert"] = () =>
{
var alerts = (IList<Alert>)controller.TempData[TempDataKeys.AlertCollection];
alerts[0].Message.should_be_same("Login successful");
alerts[0].AlertType.should_be(AlertType.Success);
};
it["Should return a redirect result"] = () => actionResult.should_cast_to<RedirectToRouteResult>();
};
context["External login already exists for another user"] = () =>
{
before = () => authService.Setup(x => x.ExternalLoginExistsForAnyOtherUser(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Returns(true);
it["Adds an error alert"] = () =>
{
var alerts = (IList<Alert>)controller.TempData[TempDataKeys.AlertCollection];
alerts[0].Message.should_be_same("The external login you requested is already associated with a different user account");
alerts[0].AlertType.should_be(AlertType.Error);
};
it["Should return a redirect result"] = () => actionResult.should_cast_to<RedirectToRouteResult>();
};
This approach seemed to work magnificently until I prepared to write test code for my ApplicationServices layer, to which I delegate viewmodel manipulation from my MVC controllers, and which coordinates the operations of the lower data repository layer:
public void CreateUserAccountFromExternalLogin(RegisterExternalLoginModel model)
{
throw new NotImplementedException();
}
public void AssociateExternalLoginWithUser(string userName, string provider, string providerUserId)
{
throw new NotImplementedException();
}
public string GetLocalUserName(string provider, string providerUserId)
{
throw new NotImplementedException();
}
I have no idea what in the world to name the test class, the test methods, or even if I should perhaps include the testing for this layer into the test class from my large code snippet above, so that a single feature or user action could be tested without regard to architectural layering.
I can't find any tutorials or blog posts which cover more than simple examples, so I would appreciate any recommendations or pointing in the right direction. I would even welcome "your question is invalid"-type answers as long as some explanation is provided.