First we need to understand a little bit about what TDD (Test Driven Development) actually is.  In general, think of it as a test-first approach to writing software.  I consider it a great approach for MVVM, since it ensures all your ViewModels are testable, and in theory, keeps your entire application testable.

Let's take a quick look at TDD's workflow:

  1. Write a test for a new feature
  2. Compile - probably fails due to missing classes/methods
  3. Implement enough new code to compile (or use Refactoring menu)
  4. Run the test and see it fail
  5. Implement enough new code to pass the test
  6. Repeat...

Dynamic can streamline this a little bit.  

Let's refactor your TDD workflow assuming your tests use dynamic extensively:

  1. Write a test for a new feature, using dynamic when referencing the new code you are about to write
  2. It compiles!  So go ahead and run the test to see it fail.
  3. Implement enough new code to pass the test
  4. Repeat...

I like that workflow much better.

Sounds great, what about mocking?

Let's say you are writing a WPF app, and need to manage child windows.  When unit testing, you absolutely don't want to popup a UI during your tests.  So how do you go about it?

In the past, you would:

  1. Come up with a simple interface for popping up a window
  2. Implement the interface for use in your main app
  3. Implement a fake interface for use in testing

This is called mocking.  And works great for alot of scenarios.

But what if we used dynamic instead?

  1. Your application references your window via the dynamic type
  2. In real life, your code just works
  3. In unit testing, you can setup your IoC/DI container to pull in fake objects

This is certainly an improvement.  And NoMvvm can make it even easier!

Let's look at an example of a test for a ViewModel.  We want to step through the Next() method a few times until we get to the end.  This is an excerpt from one of NoMvvm's unit tests/samples found here.

    public class WizardTest
        public WizardTest() 
            var container = new TinyIoCContainer();
            var adapter = new Adapter(container);

            adapter.AddView("Main", typeof(Blob), typeof(MainViewModel));
            adapter.AddView("First", typeof(Blob), typeof(FirstViewModel));
            adapter.AddView("Second", typeof(Blob), typeof(SecondViewModel));
            adapter.AddView("Third", typeof(Blob), typeof(ThirdViewModel));
            adapter.AddView("Fourth", typeof(Blob), typeof(FourthViewModel));
            adapter.AddView("Popup", typeof(Blob), typeof(PopupViewModel));

        public void Next_Whole_Way()
            dynamic main = IoC.GetViewModel("Main");


            Assert.IsInstanceOfType(main, typeof(MainViewModel));
            Assert.IsInstanceOfType(main.Child, typeof(FourthViewModel));

Notice what we did here:

  • Even our unit testing uses IoC and dependency injection
    • This keeps things flexible in your tests and your application and also tests your dependency injection at the same time.
  • The Wizard sample app uses Ninject as its IoC container, but the test uses TinyIoC
    • Remember, NoMvvm is IoC-container independent, I chose Ninject in production but TinyIoC in testing.  Choose what you want, NoMvvm doesn't want to constrain you.
  • The test invokes all methods directly, as if they were normal methods
  • What is the Blob?

Blob is our hero here.  It will successfully allow the invocation of any property, indexer, method, operator, etc. with no errors--as long as you use dynamic.

Let's look at an interesting unit test of Blob in NoMvvm:

        public void Call_Method()
            dynamic blob = new Blob();
            dynamic result = blob.I().Can().Call().Any().Method().I().Want().And().It().Wont().Blow().Up();

This makes Blob able to mock anything and enables us to use it for any View we wish to be mocked in our testing.

Additionally you can inherit Blob, if you need to do something specific:

public class FakeWebService : Blob
    public int CountRecords(string ID) { return 2000; }

Blob will still handle non-implemented methods.  Non implemented methods return Activator.CreateInstance() of the desired return type, so you would also need to subclass if there is no default constructor for a certain return type.

ASIDE: If you need to reuse the Runtime setup in a unit test's constructor across multiple tests, I'd recommend an extension method for Runtime to be called in all your tests constructors (or TestInitialize).

Last edited Sep 12, 2011 at 8:02 PM by jonathanpeppers, version 9


No comments yet.