-
Notifications
You must be signed in to change notification settings - Fork 21
Calling Original Method
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.
-
Home
- QUICK TOUR [SRC]
- FEATURES
- CHEAT SHEET
- PACKAGE MANAGER CONSOLE POWERSHELL REFERENCE
- COMMAND LINE REFERENCE
- APPVEYOR SUPPORT
- MIGRATION
- From Microsoft Research Moles [SRC]
- From Microsoft Fakes [SRC]
- From Telerik JustMock [SRC]
- From Typemock Isolator [SRC]