8

I want to test my AccountController. The problem is that in Register method I use the next line to create the user:

Membership.CreateUser(model.Email, model.Password, model.Email, null, null, true, null, out createStatus);

In the web application I use a CustomMembershipProvider which I set using web.config. In my unit test Membership class in standard SqlMembershipProvider. And not my CustomMembershipProvider that I use in my app.

How can I set up membership in unit test context? I mean to set it up programatically as asp net set it after reading web.config file.

I already use interface for mocking users management data layer but I was thinking if there is a way to avoid interface in this case. To be able to set up a mock implementation of membership in unit test.

public void RegisterTest()
{
    IUsersManager repository = new Tests.Data.UsersManager();
    AccountController target = new AccountController(repository); 
    //be able to set Membership underlying provider
    Membership.Provider = new MockMembershipProvider();
}

3 Answers 3

8

Define a membership interface, something like this:

public interface IMembershipProvider
{
    void CreateUser(string username, string password);
}

...implement it for your MVC application like this:

public class AspDotNetMembershipProvider : IMembershipProvider
{
    public void CreateUser(string username, string password)
    {
        string createStatus;

        Membership.CreateUser(
            username,
            password,
            username,
            null,
            null,
            true,
            null,
            out createStatus);

        // throw an exception if createStatus isn't as expected
    }
}

...then inject it into your controller and use it like this:

public class AccountController
{
    private readonly IMembershipProvider _membershipProvider;

    public AccountController(IMembershipProvider membershipProvider)
    {
        this._membershipProvider = membershipProvider;
    }

    public ActionResult Register(RegistrationModel model)
    {
        // Try and catch this, returning a success ActionResult if it worked:
        this._membershipProvider.CreateUser(model.Email, model.Password);
    }
}

ASP.NET uses static classes like Membership for a number of things, but static class access always makes unit testing difficult. The standard solution is to define an interface for the service, implement it using the static ASP.NET class, and inject it into your controllers.

You can set up the injection (if you've not already) by using a default DependencyResolver and a DI container like Unity.

Sign up to request clarification or add additional context in comments.

3 Comments

Shouldn't AspDotNetMembershipProvider implement the interface ? like public class AspDotNetMembershipProvider : IMembershipProvider { ...
Membership class is static ... but the underlying provider is set by asp runtime ... and I was thinking that I can also set it up
Directly accessing the static Membership class from your AccountController is not an optimal design decision in my opinion; if you're already using injection for repositories, I'm not sure why you'd want to avoid it for other dependencies? As mentioned by @PHeiberg, your tests running through Microsoft's Membership system makes them more integration than unit tests, and is unnecessary; Microsoft have tested that bit for you. Your controller depending on Membership without having it injected gives it a hidden dependency, which you should always avoid.
2

I've created a more structured membership provider which have been breaken down into three different interfaces (and the provider is resolving them using DependencyResolver).

It makes it easy to unit test the provider. Just test your implementation of the IAccountRepository.

You can read about it here: http://blog.gauffin.org/2011/09/a-more-structured-membershipprovider/

or just install the nuget package:

install-package griffin.mvccontrib

Comments

0

Your Unit test of the controller should not use your CustomMembershipProvider or the standard SQL provider. If performing a Unit test you should use a Stub/Fake that you have complete control over what it's returning. If you're using the real provider in the test it's no longer a Unit test, it's an integration test.

In order to achieve Unit testability you need to define a Fake provider, either using a Mocking framework or by hand rolling a fake with "canned" results. The fake could look something like this:

public class FakeMembershipProvider : MembershipProvider
{
   public MembershipCreateStatus CreateStatus = MembershipCreateStatus.Success;

   public void CreateUser((string username, string password, string email, 
       string passwordQuestion, string passwordAnswer, bool isApproved, 
       object providerUserKey, out MembershipCreateStatus status)
   {
      status = CreateStatus;
   }

   ...
}

Let the controller take the provider as a ctor argument

public class AccountController
{
    private readonly MembershipProvider _membershipProvider;

    public AccountController(MembershipProvider membershipProvider)
    {
        _membershipProvider = membershipProvider;
    }

    public ActionResult Register(RegistrationModel model)
    {
        MembershipCreateStatus result;
        _membershipProvider.CreateUser(model.Email, model.Password, ..., out result);

        return View(/*Make this depend on the result*/);
    }
}

In the Unit test you want to setup the fake according to what you want to test and you can assert the outcome that you expect for each registration result:

[Test]
void Should_display_success_view_when_user_successfully_created()
{
    var membershipProvider = new FakeMembershipProvider();
    membershipProvider.CreateStatus = MembershipCreateStatus.Success;
    var controller = new AccountController(membershipProvider);
    var model = new RegistrationModel();

    var result = controller.Register(model) as ViewResult;

    Assert.That(result.Name, Is.EqualTo("ExpectedViewName"));     
}

Since the MemberShipProvider is quite large and you're probably not going to use all of it it can be wise to use @SteveWilkes approach of wrapping the Membership class instead to create a smaller more targeted interface. Also a mocking framework save you a lot of work. In order to wire up your controller with the new dependency you have to create a new ControllerFactory.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.