Skip to content

Calling Original Method

Akira Sugiura edited this page Oct 12, 2015 · 10 revisions

Depending on a test case, you may think that "I want to call original method, because I want to verify only the passed value." or "I want to just call indirectly at nth time". The feature for achieving it is IndirectionsContext.ExecuteOriginal. It is same as MS Fakes feature ShimsContext.ExecuteWithoutShims.

The example "I want to call original method..." is listed in the section "Validating (private) Implementation Details" of the article "Better Unit Testing with Microsoft Fakes". Unfortunately, I think it is too difficult. So, let I explain it in a smaller example:

using System;
using System.Collections.Generic;
using System.Linq;

namespace CallingOriginal
{
    public class Village
    {
        public Village()
        {
            var r = new Random(DateTime.Now.Second);    // !?!?!?!?!?!?
            var count = r.Next(100);
            for (int i = 0; i < count; i++)
                m_ricePaddies.Add(new RicePaddy(i, r));

            for (int i = 0; i < count; i++)
            {
                for (int j = i + 1; j < count; j++)
                {
                    var distance = r.Next(100);
                    m_roads.Add(new FarmRoad(m_ricePaddies[i], m_ricePaddies[j], distance));
                    m_roads.Add(new FarmRoad(m_ricePaddies[j], m_ricePaddies[i], distance));
                }
            }
        }
...(snip)...

Village object generates RicePaddy objects less than 100 and FarmRoad objects that link them randomly in the constructor. In addition, the distance between RicePaddy objects is set to FarmRoad object.
...Hmm, is it little strange? When viewed on initializing Random object, the default constructor is not used, but the constructor that Seed is passed is used purposely, then DateTime.Now.Second is passed to it. This will easily get same Seed, so many Village objects will be generated, but they will have same value!

Well, I believe that the reviewer doesn't reject even if you correct extemporaneously the program of this scale. However, the correction that protects existing behavior of the code against unintended changes via automated testing is more reliable, isn't it? --- the difficulty in the article of Fakes is perhaps purpose to bring effect into view, I think 😜

Automated test example is like below:

...(snip)...
        [Test]
        public void Constructor_shall_not_call_within_short_timeframe_to_generate_unique_information()
        {
            using (new IndirectionsContext())
            {
                // Arrange
                var seeds = new HashSet<int>();
                PRandom.ConstructorInt32().Body = (@this, seed) =>
                {
                    IndirectionsContext.ExecuteOriginal(() =>
                    {
                        var ctor = typeof(Random).GetConstructor(new[] { typeof(int) });
                        ctor.Invoke(@this, new object[] { seed });
                    });
                    seeds.Add(seed);
                };
                new Random();  // preparing JIT
                seeds.Clear();


                // Act
                var vil1 = new Village();
                Thread.Sleep(TimeSpan.FromSeconds(1));
                var vil2 = new Village();


                // Assert
                Assert.AreEqual(2, seeds.Count);
            }
        }
...(snip)...

Stubbing operation hasn't been optimized yet, so you have to prepare JIT unlike in the case of the Fakes example(such as new Random();). In addition, calling method in a short span of time is difficult(such as Thread.Sleep(TimeSpan.FromSeconds(1));). However, the intent that is same as the Fakes example can be expressed in the above example.

After you protect its behavior via automated testing, let's confirm test failure by comment out the parts of the test Thread.Sleep(TimeSpan.FromSeconds(1));. This will guarantee to correct the bug reliably! --- Oh, now I remember. You will find immediately that the idea that uses Environment.TickCount(default constructor) can't pass the test. Actually, this counter is much less accurate. It is a seemingly easy example, but it has pitfalls in fact 😲

Please use carefully against struct. The signature of the Indirection Delegate is changed to ref parameter, because we will get just a copied value if the signature is same as class. For example, let me say about the following code:

using System;

namespace CallingOriginal
{
    public class RicePaddy
    {
        internal RicePaddy(int identifier, Random r)
        {
            Identifier = identifier;
            var yield = r.Next();
            m_yield = yield % 10 == 0 ? default(int?) : yield * 1000;
        }
    ...(snip)...

If you want to know how to initialize in the constructor of Nullable<int>, :

    ...(snip)...
        [Test]
        public void Constructor_should_be_initialized_by_non_null_if_number_that_is_not_divisible_by_10_is_passed()
        {
            using (new IndirectionsContext())
            {
                // Arrange
                var actualValue = 0;
                PRandom.Next().Body = @this => 9;
                PNullable<int>.ConstructorT().Body = (ref Nullable<int> @this, int value) =>
                {
                    actualValue = value;
                    @this = IndirectionsContext.ExecuteOriginal(() => new Nullable<int>(value));
                };


                // Act
                var paddy = new RicePaddy(1, new Random());


                // Assert
                Assert.AreEqual(9000, actualValue);
            }
        }
    ...(snip)...

the test code is became as the above. You can initialize by replacing the first parameter.

By the way, I searched as much as possible whether MS Fakes supports this feature, but it doesn't... Well, if it is really true, Microsoft maybe designed it like so, because the feature is not necessary very much.

Complete source code is here.