chevron-thin-right chevron-thin-left brand cancel-circle search youtube-icon google-plus-icon linkedin-icon facebook-icon twitter-icon toolbox download check linkedin phone twitter-old google-plus facebook profile-male chat calendar profile-male
Welcome to Typemock Community! Here you can ask and receive answers from other community members. If you liked or disliked an answer or thread: react with an up- or downvote.
0 votes
I am new to TDD and to TypeMocks. Having done the simple examples I tried to write a test for some existing code - below.

I have some general questions:
1) Have I adopted broadly the right approach? I know that without mock the objects and calls, the test throws exceptions. I simply found pragmatic found ways to eliminate these exceptions.
2) If I am on the right lines then it seems that the test is based on anticipating the code within the function being tested. For example, that the calls IsDirty, State and ContainsData occur in that order when, at least theoretically, they could be called in a different order. Doesn't this contradict the idea of writing the tests first so you wouldn't necessarily know the structure of the code to be tested. Also some of the mock objects are there only to prevent these exceptions and you are unlikely to know that they are needed until you have written the code which presumably means that you have to add these mock objects to the test as you develop the real code. Is this right?
3) How do you respond to the comments I have received from colleagues that there is a lot of code just to test that given a set of property values the correct call is made. Given that there would be several other tests of similar complexity to test the rest of the SaveFeeIncome method.

Any comments would be welcome.

John

The live code

         private static string SaveFeeIncome(DataMarshaller FeeIncome)
        {
            string error = string.Empty;
            IDbTransaction trans = null;
            bool commit = false;

            if (FeeIncome.IsDirty) //only try and perform save if outstanding changes
            {
                trans = EBSShared.CreateTransaction();
                IDbConnection conn = trans.Connection;

                try
                {
                    // Delete
                    if (FeeIncome.State == DataMarshallerState.Deleted)
                    {
                        if (FeeIncome.ContainsData) //Delete if its not a new record
                        {
                            error = TribalTech.EBS.Process.Deletion.Delete(FeeIncome);

                            if (error != string.Empty)
                                error = String.Format(saveErrorMsgTemplate, FeeIncome[Columns.Id].Value, FeeIncome[Columns.Type].Value, error);
                        }
                    }
                    else
                    {
                        // Insert and Update code omitted
                    }
                }
                catch (Exception exception)
                {
                    if (log.IsErrorEnabled)
                        log.Error(string.Format(logSaveFailure), exception);
                    //preserve the error message in case of error
                    error = String.Format(errorMsgTemplate, setValue.UniqueKey, exception.Message);
                    commit = false;
                }
                finally
                {
                    TribalTech.EBS.Process.Shared.CleanupTransaction(trans, conn, commit);
                }
            }

            return error;
        }

    }


My first attempt at a test

        [Test]
        public void TestSaveFeeIncomeDelete()
        {
            // Get the Method Info for private static method called 'SaveFeeIncome'.
            // Later this Method Info will be used to Invoke the SaveFeeIncome method.
            Type feesType = typeof(Fees);
            MethodInfo saveFeeIncomeMethodInfo = feesType.GetMethod("SaveFeeIncome", BindingFlags.NonPublic | BindingFlags.Static);

            // Create a mock Data marshaller 
            MockObject mockDataMarshaller = MockManager.MockObject(typeof(DataMarshaller));
            // Tell the mock Data Marshaller to expect the following property calls...
            mockDataMarshaller.ExpectGet("IsDirty", true); // The IsDirty property is read only, so it would not be possible to set it anyway.
            mockDataMarshaller.ExpectGet("State", DataMarshallerState.Deleted);
            mockDataMarshaller.ExpectGet("ContainsData", true);
            // Cast the Mock to 'Real' Data Marshaller
            DataMarshaller aDataMarshaller = (DataMarshaller)mockDataMarshaller.Object;
            // Set the arguments object array for use later when the 'SaveFeeIncome' method is invoked
            Object[] arguments = new Object[] { aDataMarshaller };

            // Create a Mock EBSShared object and make it expect a CreateTransaction
            // call that will return a mock transaction which is then cast to 'real' one.
            MockObject mockEbsShared = MockManager.MockObject(typeof(EBSShared));
            MockObject mockTransaction = MockManager.MockObject(typeof(IDbTransaction));
            IDbTransaction realTransaction = (IDbTransaction)mockTransaction.Object;
            mockEbsShared.ExpectAndReturn("CreateTransaction", realTransact
asked by johnkirby (1.6k points)

1 Answer

0 votes
Hi John,
I can only give you my personal advice.
You have a few issues with your test.
1. What are you testing?
2. Why are you testing a private member?
3. Why can't you instantiate a DataMarshaller Normally?

I personally would refactor the code and make the code clearer, but in any case once you get this method out of the way, the path to testing the rest of your system is clear, because you can mock the SaveFeeIncome() method.

In any case we can create helper methods.Here is a way to do it that I think is clearer. I am using Enterprise Features.

// create a fake DataMarshaller - if this was possible just 
// do a new DataMarshaller() and set the correct properties.
private DataMarshaller CreateFakeDataMarshaller(bool isDirty, DataMarshallerState state, 
    bool containsData)
{
   DataMarshaller fakeDataMarshaller  = 
    (DataMarshaller)RecorderManager.CreateMockObject(typeof(DataMarshaller ));
   using (RecordExpectations rec = RecorderManager.StartRecording())
   {
        // this is a stub we don't really care if these are called or not
        // if it was possible we would just create an instance
        rec.DefaultBehavior.RepeatAlways()

        rec.ExpectAndReturn(fakeIncomeFee.IsDirty,isDirty);
        rec.ExpectAndReturn(fakeIncomeFee.State,state);
        rec.ExpectAndReturn(fakeIncomeFee.ContainsData,containsData);
   }
   return fakeDataMarshaller;
}

If you where using Visual Studio Unit Test, this accessor would be created automatically
private string Call_Fee_SaveFeeIncome(DataMarshaller fee)
{
    return (string)typeof(Fees).GetMethod("SaveFeeIncome",
      BindingFlags.NonPublic | BindingFlags.Static).Invoke(null, 
      new object[]{fee});
}

Here is the test
[Test]
public void SaveFeeIncomeGetsDeletedWhenStateDeletedAndHasData()
{
   // setup our fake income fee
   DataMarshaller fakeIncomeFee = CreateFakeDataMarshaller(
     true /*dirty */,DataMarshallerState.Deleted,true/*contains data*/);

   // stub connections
   using (RecordExpectations rec = RecorderManager.StartRecording())
   {
        // this is a stub we don't really care if these are called or not
        rec.DefaultBehavior.RepeatAlways()
        IDbConnection fakeConnection = 
             EBSShared.CreateTransaction().Connection;
        TribalTech.EBS.Process.Shared.CleanupTransaction();
   }

   // make sure that delete gets called and it passes our income fee
   // no error is returned
   using (RecordExpectations rec = RecorderManager.StartRecording())
   {
         TribalTech.EBS.Process.Deletion.Delete(fakeIncomeFee); 
         rec.Return("").CheckArguments();
   }
   
   // Call our private method.
   string returnValue =Call_Fee_SaveFeeIncome(fakeIncomeFee)
   // Test the result
   Assert.AreEqual(returnValue, string.Empty);

   MockManager.Verify();
}


Of course you can then test that the connection is always closed and that errors are handled correctly and that the insert/update is ok.

You then know that this method works well and can mock out the database by mocking SaveFeeIncome() for all the other tests.

You might find an urge to refactor your code, and to throw errors instead of returning them.
Your code might look like this:
private static void SaveFeeIncome(DataMarshaller feeIncome)
{
    if (ShouldDelete(feeIncome))
    {
         try {
            Delete(feeIncome);
         } catch (Exception exception) // are you sure you want to catch all exceptions?
         {
             throw new SaveException(saveErrorMsgTemplate,feeIncome,exception);
         }
    }      
    // Insert and Update code omitted
}
private bool ShouldDelete(DataMarshaller feeIncome)
{
   return feeIncome.IsDirty && 
      feeIncome.State == DataMarshallerState.Deleted && 
      feeIncome.ContainsData;
}
private void Delete(DataMarshaller feeIncome)
{ 
   IDbTransaction trans = EBSShared.CreateTransaction();
   IDbConnection conn = trans.Connection;
   try {
      TribalTech.EBS.Process.Deletion.Delete(feeIncome);
   } finally
   {
       TribalTech.EBS.Process.Shared.CleanupTransaction(trans, conn, false);
    } 
}
answered by scott (32k points)
...