Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit Testing a Login in ASP.NET

I'm very new to TDD and I'm having trouble with one of my unit tests. I just can't seem to understand what to do next. :( I'm trying to unit test a service of mine called AccountService and I'm testing a method called DoLogin(username, password). Here's some example code:

    [Test]
    public void User_With_Correct_Username_And_Pass_Should_Login_Successfully()
    {
        // Arrange
        var accountService = new AccountService();

        // Act
        bool result = accountService.DoLogin("test", "test");

        // Assert
        Assert.IsTrue(result);
    }

    public class AccountService : IAccountService
    {
      public bool DoLogin(string username, string password)
      {
        if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
            return false;

        return true;
      }
    }

So this test pasts but now what do I do?! How do I really test if a valid login occured? Do I need to implement an integration test and test the login against a real or in-memory DB? Sorry if I'm doing things totally incorrect. I really hope one day to get this TDD stuff down. Thanks

like image 292
CalebHC Avatar asked Jun 04 '09 21:06

CalebHC


1 Answers

Your experience is very similar to mine starting out. While I am sold on TDD and wouldn't do thing any differently, I certainly understand your confusion. It is important to remember that TDD is a design philosophy. That being said, I think I can help to clear up some of your frustrations.

  1. Start by thinking about what you are trying to accomplish, not on an individual test level, but what is it you are trying to do. If your task (user story) involves grabbing some credentials and attempting to authenticate the current user based on those credentials, then start from there and work your way down. You seem to be heading in this direction, only getting stuck on the next steps

  2. When working on an individual test, think of it in terms of expected behavior instead of just verifying some inputs and outputs. Think of yourself as using this component, and simply write a line of code the way you want it to be written. Let this part help to drive the interface/contract of your service. You have to ask yourself the question, "If I were to call this method, how would I know it worked? What would I expect it to do?" This will determine what kind of assertions you need to make.

  3. Determine what your external dependencies are, and utilize abstractions instead (dependency inversion principle). If these dependencies are something you care about as part of your behavior verification, then use dependency injection so you can use a mock in your test.

  4. Always, always, always follow this order [Write your test, watch it fail, code to pass, refactor]. LEARN FROM MY MISTAKES!!! Trust me, this is non-negotiable. Otherwise, you are liable to blame your problems on TDD when it isn't being employed properly.

Ok, so putting this all together with your example, and some nicely provided test cases from lance, we could do something like this:

[Test]
public void ShouldAuthenticateValidUser()
{
    IMyMockDa mockDa = new MockDataAccess();
    var service = new AuthenticationService(mockDa);

    mockDa.AddUser("Name", "Password");

    Assert.IsTrue(service.DoLogin("Name", "Password"));

    //Ensure data access layer was used
    Assert.IsTrue(mockDa.GetUserFromDBWasCalled);
}

[Test]
public void ShouldNotAuthenticateUserWithInvalidPassword()
{
    IMyMockDa mockDa = new MockDataAccess();
    var service = new AuthenticationService(mockDa);

    mockDa.AddUser("Name", "Password");

    Assert.IsFalse(service.DoLogin("Name", "BadPassword"));

    //Ensure data access layer was used
    Assert.IsTrue(mockDa.GetUserFromDBWasCalled);
}

Ok, so there is a lot going on there, and perhaps a lot to research. However, you can begin to see how it is possible to do thorough testing by using better design. In the examples above, it is important to note that the Mock Object is custom rolled, but you don't have to go through all this pain. There are many Mocking Frameworks out there. For instance using RhinoMocks, your test would look like this:

[Test]
public void ShouldAuthenticateValidUser()
{
    var mockRepo = new MockRepository();
    var mockDa = mockRepo.DynamicMock<IMyMockDa>();

    var service = new AuthenticationService(mockDa);

    using(mockRepo.Record())
    {
        //I realize this is a terrible method and should not exist if you
        // care about security, but for demonstration purposes...
        Expect.Call(mockDa.GetPassword("User")).Return("Password");
    }
    using(mockRepo.Playback())
    {
        Assert.IsTrue(service.DoLogin("User", "Password"));
    }
}

Get used to doing things the manual way first so you understand the concepts, and then move on to using a framework. Whew! Lots of info, but as you can see, TDD is an entire design philosophy. It will however, result in cleaner code, better design, and fewer bugs.

like image 78
Josh Avatar answered Oct 06 '22 01:10

Josh