2
votes

I have a unit test to check my AccountController.LogIn method. A redirect result is returned to indicate successs, otherwise a viewresult is returned.

The test always fails as the return type is always viewresult, even though the test should return success as the credentials are valid, however I can't identify where the problem is. My TestMethod: CustomerRepositoryTest.cs

[TestMethod]
public void Can_Login_With_Valid_Credentials()
{
     //  Arrange
     Mock<IAddressRepository> mockAddressRepository = new Mock<IAddressRepository>();
     Mock<ICustomerRepository> mockCustomerRepository = new Mock<ICustomerRepository>();
     Mock<IOrderRepository> mockOrderRepository = new Mock<IOrderRepository>();

     LoginViewModel model = new LoginViewModel
     {
         Email = "[email protected]",
         Password = "password"
     };

     AccountController target = new AccountController(mockCustomerRepository.Object, mockAddressRepository.Object, mockOrderRepository.Object);

   //  Act
    ActionResult result = target.LogIn(model);

    //  Assert
    Assert.IsInstanceOfType(result, typeof(RedirectResult));
    Assert.AreEqual("", ((RedirectResult)result).Url);
    }

When I run the test, it fails in My AccountController Login method when I call ValidateUser AccountController.cs

if (Membership.ValidateUser(LoginModel.Email, LoginModel.Password))
{
  ...
 return RedirectToRoute(new
 {
    controller = "Account",
    action = "Details"
 });
}
else
{
   return View();
}

My custom MembershipProvider ValidateUser looks like this:

AccountMembershipProvider.cs

public class AccountMembershipProvider : MembershipProvider
{
     [Inject]
    public ICustomerRepository repository { get; set; }

    public override bool ValidateUser(string username, string password)
    {
       var cust = repository.GetAllCustomers().SingleOrDefault..

When I run the application normally i.e. not testing, the login works fine. In the application I inject the CustomerRepository into the custom membership provider in Global.asax:

    public class MvcApplication : System.Web.HttpApplication
    {
      private IKernel _kernel = new StandardKernel(new MyNinjectModules());

      internal class MyNinjectModules : NinjectModule
      {
        public override void Load()
        {
            Bind<ICustomerRepository>().To<CustomerRepository>();
        }
      }

      protected void Application_Start()

  {
   _kernel.Inject(Membership.Provider);
   ...

Is it the case that the Global.asax code isn't run while unit testing? and so my custom provider isn't being injected, hence the fail?

UPDATE I mocked my Provider class and passed the mocked CustomerRepository object to it.

Mock<AccountMembershipProvider> provider = new Mock<AccountMembershipProvider>();

provider.Object.repository = mockCustomerRepository.Object;

I then created a setup for the method I'm trying to test:

 mockCustomerRepository.Setup(m => m.IsValidLogin("[email protected]", "password")).Returns(true);

But unfortunately I'm still getting a fail every time. To answer the question about whether I need a real or mocked object for the test - I'm not fussy, I just want to get it working at the moment!

UPDATE 2

I made those changes, and while it's still failing, it has allowed me to identify the specific problem. While debugging the test, I discovered that when I call the overridden

Membership.ValidateUser(LoginModel.Email, LoginModel.Password)

The Membership.Provider is of type SqlMembershipProvider (which is presumably the default type) and consequently validation fails.

If I cast the provider to my custom provider...

((AccountMembershipProvider)Membership.Provider).ValidateUser(LoginModel.Email, LoginModel.Password)

I get an InvalidCastException when running the test. So it seems that my mocked AccountMembershipProvider isn't being used for the test and instead the default provider is being used.

I think you have identified this already in the comment:

// set your mock provider in your AccountController

However I'm not sure what you mean exactly - I don't have a property on my AccountController to assign the provider to, and i'm not injecting it into the constructor.

2
Just to let you know, I added more information to my answer earlier in the day with an edit. Do not hesitate to ask for clarifications or more specific information.Gilles
@Gilles - I implemented your suggestions (see update). They clarified a lot for me, although still failing every time unfortunately! Do I need to define setup methods for both the Provider AND the Repository classes?markpsmith
I update my post with an example.. Basically if your provider is mocked there is no need to create mocks for it's dependencies as the mock object won't call them.Gilles
@Gilles - See updates. The test is still using the default provider instead of my custom one. I do appreciate your help on this, thank you.markpsmith
In Membership.ValidateUser, is ValidateUser a static method ?Gilles

2 Answers

3
votes

Your original question: "Is it the case that the Global.asax code isn't run while unit testing? and so my custom provider isn't being injected, hence the fail?"

My answer:

Yes.

The global.asax file is used by ASP.Net and ISS at run-time. It is compiled when the server receives it's first request.

When you are testing, you aren't in the context of a ASP.Net web-application running in ISS, but rather in program running in a test session. This means your global.asax won't get called.

More importantly, when you call:

Mock<ICustomerRepository> mockCustomerRepository = new Mock<ICustomerRepository>();

Ninject won't get called the fill the import. Moq will create a mock based on the interface. No real object will be created.

Since you did not define any Setup methods, ie:

mockCustomerRepository.Setup(mock => mock.MyAuthenticateMethod()).Returns(true);

You are passing around a mock with no defined behaviour. By default these will return false. Which probably explains why you are always getting a viewresult.

What you need to do is define setup methods for the methods you need to mock.

These are the methods of CustomerRepository that will get called you when call:

target.LogIn(model);

Also note that your AccountMembershipProvider won't get it's CustomerRepository injected since NInject won't be used. If you are testing the AccountController and it's not static (Moq doesn't work with static) you should consider mocking the AccountMembershipProvider. If you can't, then you would need to supply your mocked instance of CustomerRepository to AccountMembershipProvider.repository in your tests.

Another solution, instead of creating a Moq mock, you could also manually create (with new) a real instance of CustomerRepository in your test.

You could have Ninject do it, but at this point why? You can create it yourself and you know what specific type to create.

It all boils down to if you need a mock or a real instance of the object for this test.

Update:

If your provider is mocked, there is no need to set the repository on it. When you are calling a mock, the real object won't get called.

What you need to do is something like this:

Mock<AccountMembershipProvider> provider = new Mock<AccountMembershipProvider>();
// set your mock provider in your AccountController

provider.Setup(m => m.ValidateUser("[email protected]", "password")).Returns(true);

Update 2:

I think you are pretty close to making your test work. Without seeing all of your code (test and class under test) I can't really give you anymore help. I also feel I answered your original question, but if you are still stuck you might get more help by asking a new question relating to the problem you are currently tackling.

1
votes

In your code you only create controller, but not run initialization for membership. I recommend you to create your own UserService with method ValidateUser and other you need instead of static class Membership usage.