Unit Test

One limitation of web sites is that you cannot create a unit test project and reference to the web site to test classes in the web site. The Rabbit Framework introduced a Test Page for unit testing within the web site. It provides a familiar way of writing unit tests.

[TestClass]
public class SampleTestClass
{
    [TestMethod]
    public void TestMothod1()
    {
      Assert.IsTrue(true);
    }
    [TestMethod]
    public void TestMothod2()
    {
      Assert.IsFalse(false); 
    }
    [TestMethod]
    public void TestMothod3()
    {
       string s1 = "a"; 
       string s2 = "a"; 
       string s3 = "a "; 

       Assert.AreEqual(s1, s2); 
       Assert.AreNotEqual(s1, s3); 
    }
}

Once saved the unit test class in App_Code, it will appear on the Test page. The Test page is integrated with the admin pages.

Unit Testing 2.png

The Rabbit Framework provides the following URL patterns for running unit tests.
  • ~/Test the entry page to unit testing
  • ~/Test/All lists all test classes and test methods
  • ~/Test/TestClass runs test methods within the test class
  • ~/Test/TestClass/TestMethod runs a test method within the test class


Mock, Stub and fake, all in one.

Since Rabbit Framework embraces and uses dynamic everywhere as possible. Mocking became very easy. The example below shows a test that does a few things:
  • Pass mock repository to PageModel class
  • Setup expectations
  • Trigger model execution
  • Verify to ensure expectation is met
    [TestMethod]
    public void SaveAs_Should_Validate_New_Id_Exists()
    {
        dynamic data = new ExpandoObject(); //create a DTO
        data.Id = "old-id";
        var repository = new Mock();
        repository.Setup("Exists"new object[] { "new-id" }, true); ////tell the model new id exists

        var model = new PageModel();
        model.Repository = repository; //push the mock to model 

        model.SaveAs(data, "new-id"); //trigger the model 

        Assert.IsTrue(model.HasError); //make sure model report error
        repository.Verify(); //make sure reporsitory.Exists is called
    }
This is the SaveAs function to be tested.
    public PageModel SaveAs(dynamic item, string newId)
    {
        Assert.IsTrue(item != null);
        var oldId = item.Id as string;
        Value = item;
        Value.Id = newId;
        Validate();
        if (HasError) return this;
 
        if (oldId != newId)
        {
            if (Repository.Exists(newId))
            {
                Errors.Add("Id"string.Format("{0} exisits already.", Value.Id));
            }
            else
            {
                Repository.Delete(oldId);
                Value.Id = newId;
            }
        }
 
        if (!HasError) Repository.Save(Value.Id as string, Value);
        return this;
    }

Mock Implementation

Mocking an interface is difficult. It requires emitting classes dynamically, like what Moq does. Mocking a concrete class is more difficult...

But mocking dynamic object is very easy. The Mock class in Rabbit Framework probably is the world's simplest mock implementation. 160 Lines in total. No dependency to any 3rd party library. Yet it provides powerful mock testing infrastructure that allows to,
  • Verify the sequence of methods to be called
  • Verify the times of methods to be called
  • Verify the parameter count being passed to methods
  • Verify each parameter's type being passed to methods
  • Verify each parameter's value being passed to methods

Listed below are test cases for the Mock object. All cases have ExpectedException attribute, because the Mock object is expected to catch the scenario.

[TestClass]
public class MockTest
{
    /// <summary>
    /// Method1 is not called
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(UnitTestException))]
    public void TestMockMethodNotRun()
    {
        dynamic mock = new Mock();
        mock.Setup("Method1", new object[] { "a", It.IsAny<int>() }, 10);
        mock.Verify();
    }

    /// <summary>
    /// Method1 has been called already. Add more setup.
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(UnitTestException))]
    public void TestMockMethodCalled()
    {
        dynamic mock = new Mock();
        mock.Setup("Method1", new object[] { "a", It.IsAny<int>() }, 10);
        Assert.AreEqual(10, mock.Method1("a", -2));
        Assert.AreEqual(10, mock.Method1("b", -2m));
        mock.Verify();
    }

    /// <summary>
    /// Method1 is called w/ 0 parameters, expected: 2.
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(UnitTestException))]
    public void TestMockParameterCount()
    {
        dynamic mock = new Mock();
        mock.Setup("Method1", new object[] { "a", 2.5m }, 10);
        Assert.AreEqual(10, mock.Method1());
        mock.Verify();
    }

    /// <summary>
    /// Method1 parameter #2 type failed, expected: IsNotNull, actual [null].
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(UnitTestException))]
    public void TestMockParameterNotNull()
    {
        dynamic mock = new Mock();
        mock.Setup("Method1", new object[] { "a", It.IsNotNull() }, 10);
        Assert.AreEqual(10, mock.Method1("a", null));
        mock.Verify();
    }

    /// <summary>
    /// Method1 parameter #2 type failed, expected: IsNull, actual 2.
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(UnitTestException))]
    public void TestMockParameterNull()
    {
        dynamic mock = new Mock();
        mock.Setup("Method1", new object[] { "a", It.IsNull() }, 10);
        Assert.AreEqual(10, mock.Method1("a", 2));
        mock.Verify();
    }

    /// <summary>
    /// Method1 parameter #2 type failed, expected: System.Int32, actual System.Decim
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(UnitTestException))]
    public void TestMockParameterType()
    {
        dynamic mock = new Mock();
        mock.Setup("Method1", new object[] { "a", It.IsAny<int>() }, 10);
        Assert.AreEqual(10, mock.Method1("a", -2m));
        mock.Verify();
    }

    /// <summary>
    /// Method1 parameter #1 value failed, a:System.String, actual: b:System.String
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(UnitTestException))]
    public void TestMockParameterValue()
    {
        dynamic mock = new Mock();
        mock.Setup("Method1", new object[] { "a", 2.5m }, 10);
        Assert.AreEqual(10, mock.Method1("b", 2.5));
        mock.Verify();
    }

    /// <summary>
    /// set_Prop1 parameter #1 type failed, expected: System.String[], actual System.Int32
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(UnitTestException))]
    public void TestMockPropertyType()
    {
        dynamic mock = new Mock();
        mock.SetupSet("Prop1", It.IsAny<string[]>());
        mock.Prop1 = 5;
        mock.Verify();
    }

    /// <summary>
    /// set_Prop1 parameter #1 value failed, expected: 5:System.String, actual: 5:System.Int32
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(UnitTestException))]
    public void TestMockPropertyValue()
    {
        dynamic mock = new Mock();
        mock.SetupSet("Prop1", "5");
        mock.Prop1 = 5;
        mock.Verify();
    }
}

Last edited Apr 30, 2011 at 12:21 AM by yysun, version 8

Comments

No comments yet.