Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Am I just not understanding TDD unit testing (Asp.Net MVC project)?

I am trying to figure out how to correctly and efficiently unit test my Asp.net MVC project. When I started on this project I bought the Pro ASP.Net MVC, and with that book I learned about TDD and unit testing. After seeing the examples, and the fact that I work as a software engineer in QA in my current company, I was amazed at how awesome TDD seemed to be. So I started working on my project and went gung ho writing unit tests for my database layer, business layer, and controllers. Everything got a unit test prior to implementation. At first I thought it was awesome, but then things started to go downhill.

Here are the issues I started encountering:

  • I ended up writing application code in order to make it possible for unit tests to be performed. I don't mean this in a good way as in my code was broken and I had to fix it so the unit test pass. I mean that abstracting out the database to a mock database is impossible due to the use of linq for data retrieval (using the generic repository pattern).

    The reason is that with linq->sql or linq->entities you can do joins just by doing:

    var objs = select p from _container.Projects select p.Objects;
    

    However, if you mock the database layer out, in order to have that linq pass the unit test you must change the linq to be

    var objs = select p from _container.Projects
           join o in _container.Objects on o.ProjectId equals p.Id
           select o;
    

    Not only does this mean you are changing your application logic just so you can unit test it, but you are making your code less efficient for the sole purpose of testability, and getting rid of a lot of advantages using an ORM has in the first place.

    Furthermore, since a lot of the IDs for my models are database generated, I proved to have to write additional code to handle the non-database tests since IDs were never generated and I had to still handle those cases for the unit tests to pass, yet they would never occur in real scenarios.

    Thus I ended up throwing out my database unit testing.

  • Writing unit tests for controllers was easy as long as I was returning views. However, the major part of my application (and the one that would benefit most from unit testing) is a complicated ajax web application. For various reasons I decided to change the app from returning views to returning JSON with the data I needed. After this occurred my unit tests became extremely painful to write, as I have not found any good way to write unit tests for non-trivial json.

    After pounding my head and wasting a ton of time trying to find a good way to unit test the JSON, I gave up and deleted all of my controller unit tests (all controller actions are focused on this part of the app so far).

  • So finally I was left with testing the Service layer (BLL). Right now I am using EF4, however I had this issue with linq->sql as well. I chose to do the EF4 model-first approach because to me, it makes sense to do it that way (define my business objects and let the framework figure out how to translate it into the sql backend). This was fine at the beginning but now it is becoming cumbersome due to relationships.

    For example say I have Project, User, and Object entities. One Object must be associated to a project, and a project must be associated to a user. This is not only a database specific rule, these are my business rules as well. However, say I want to do a unit test that I am able to save an object (for a simple example). I now have to do the following code just to make sure the save worked:

    User usr = new User { Name = "Me" };
    _userService.SaveUser(usr);
    
    Project prj = new Project { Name = "Test Project", Owner = usr };
    _projectService.SaveProject(prj);
    
    Object obj = new Object { Name = "Test Object" };
    _objectService.SaveObject(obj);
    
    // Perform verifications
    

    There are many issues with having to do all this just to perform one unit test. There are several issues with this.

    • For starters, if I add a new dependency, such as all projects must belong to a category, I must go into EVERY single unit test that references a project, add code to save the category then add code to add the category to the project. This can be a HUGE effort down the road for a very simple business logic change, and yet almost none of the unit tests I will be modifying for this requirement are actually meant to test that feature/requirement.
    • If I then add verifications to my SaveProject method, so that projects cannot be saved unless they have a name with at least 5 characters, I then have to go through every Object and Project unit test to make sure that the new requirement doesn't make any unrelated unit tests fail.
    • If there is an issue in the UserService.SaveUser() method it will cause all project, and object unit tests to fail and it the cause won't be immediately noticeable without having to dig through the exceptions.

Thus I have removed all service layer unit tests from my project.

I could go on and on, but so far I have not seen any way for unit testing to actually help me and not get in my way. I can see specific cases where I can, and probably will, implement unit tests, such as making sure my data verification methods work correctly, but those cases are few and far between. Some of my issues can probably be mitigated but not without adding extra layers to my application, and thus making more points of failure just so I can unit test.

Thus I have no unit tests left in my code. Luckily I heavily use source control so I can get them back if I need but I just don't see the point.

Everywhere on the internet I see people talking about how great TDD unit tests are, and I'm not just talking about the fanatical people. The few people who dismiss TDD/Unit tests give bad arguments claiming they are more efficient debugging by hand through the IDE, or that their coding skills are amazing that they don't need it. I recognize that both of those arguments are utter bullocks, especially for a project that needs to be maintainable by multiple developers, but any valid rebuttals to TDD seem to be few and far between.

So the point of this post is to ask, am I just not understanding how to use TDD and automatic unit tests?

like image 761
KallDrexx Avatar asked May 19 '10 17:05

KallDrexx


People also ask

What is TDD in asp net?

TDD means writing tests to implement a requirement and continuously iterate through RED GREEN and REFACTOR cycles. Advantages. TDD makes you think with the needed API from the beginning. You need to think about what classes, properties, API's are needed. This will usually lead to great API design.

Is TDD same as unit testing?

TDD is a broader concept than unit tests. TDD is a software development approach focused on understanding the problem domain and fulfilling the requirements. Bare unit tests are about validating the written source code and avoiding bugs and regression. In fact, unit tests are part of the TDD cycle.

What is TDD in ASP NET MVC?

What is TDD? Test-Driven Development is a software development process being converted to the test cases before the application is fully developed. The application should be in a loosely coupled manner so that we can test the independent part of the application. Let's start step by step testable application in MVC.

What is TDD in unit testing?

TDD(Test-driven development) TDD is a part of agile development where developers write code only when a test fails. A programmer writes a test first, even for the smallest of functionality and runs it to get an obvious fail. Then the programmer writes code only to satisfy the test.


3 Answers

You may take a look at a sample ASP.NET MVC 2.0 project structure I wrote. It presents some concepts that might get you started on unit testing your controller logic and database. As far as database testing is concerned it is no longer unit tests but integration tests. As you will see in my sample I am using NHibernate which allows me to easily switch to a sample SQLite database that is re-created for each test fixture.

Finally doing unit tests in ASP.NET MVC could be a pain without proper separation of concerns and abstractions and using mocking frameworks and frameworks like MVCContrib.TestHelper could make your life easier.

Here's a preview of how your controller unit test might look like.


UPDATE:

As a response to the comments I think that programming is a concrete task and it is difficult to give an ultimate answer as to how do I unit test my complex business application. In order to be able to test a complex application coupling between the layers should be as weak as possible which could be achieved with interfaces and abstract classes. I agree though that achieving such a weak coupling in a complex application is not a trivial task.

I could give you an advice: if the whole TDD concept is difficult to understand and you see no benefits in it then this is OK. Nobody can prove that TDD is beneficial in all situations. Just try to design your application in such a way that each class has a single responsibility. If you find yourself doing validation of input, SQL data access and exception handling in the same class then you are doing it wrong. Once you achieve this separation you will see that unit testing becomes much easier and you could even come to a stage when unit tests will drive your development :-)

As far as unit testing a JsonResult with MvcContrib.TestHelper this is a concrete question to which I give a concrete answer:

public class MyModel
{
    public string MyProperty { get; set; }
}

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return Json(new MyModel { MyProperty = "value" });
    }
}

And the test:

[TestMethod]
public void HomeController_Index_Action_Should_Return_Json_Representation_Of_MyModel()
{
    // arrange
    var sut = new HomeController();

    // act
    var actual = sut.Index();

    // assert
    actual
        .AssertResultIs<JsonResult>()
        .Data
        .ShouldBe<MyModel>("")
        .MyProperty
        .ShouldBe("value");
}
like image 106
Darin Dimitrov Avatar answered Dec 26 '22 01:12

Darin Dimitrov


It sounds like the problem is not with unit testing, but with your tooling/platform. I come from a Java background, so I don't have to re-write my queries just to satisfy unit tests.

Testing Json is a pain in the you know what. If you don't want to test it, don't ;) Testing is not about 100% coverage. It's testing the stuff that really needs to get tested. You are way more likely to get a bug inside of a complicate if expression than adding things to a map, and then getting it converted to json.

If you test that the map is getting created properly... there is a really good chance that the json is also correct. Of course, it won't always be, but you'll know that once you run it and it works. Or not. It's really that simple.

I can provide a real comment on the 3rd problem though. You really don't want to create massive object graphs. But there some ways to do it.

Create a singleton called ObjectMother. Have methods like ObjectMother.createProject(). Inside, you create the dummy project instance. That way, 12 tests can use this method, and then you only have to change it in one spot. Remember, test code needs to be refactored!

Also, you might look into something like dbUnit. Well, that's what it's called in the Java World. The idea is that before every test is run, it puts the database in a known state. And it does this automatically in the setup/teardown of your tests. This means your test code can immediately start testing. The actual dummy test data is in a script or xml file.

I hope that helps.

like image 23
egervari Avatar answered Dec 25 '22 23:12

egervari


As far as testing your AJAXified views goes I would suggest that you try out a testing framework like WatiN.

With this you can do the following (example from their site).

[Test]
public void SearchForWatiNOnGoogle()
{
 using (var browser = new IE("http://www.google.com"))
 {
  browser.TextField(Find.ByName("q")).TypeText("WatiN");
  browser.Button(Find.ByName("btnG")).Click();

  Assert.IsTrue(browser.ContainsText("WatiN"));
 }
}
like image 45
Matt Avatar answered Dec 26 '22 00:12

Matt