moq提供了mock,使我们在做C# unit test时可以mock接口,从而减少外部依赖。
假设我们有个加法计算器程序,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class CalculateImp
{
private const string calculateProgID = "Calculate.CalculateManager";
public int DoCalculate()
{
var cal = GetCalculateInstance();
return cal.Sum(1, 2);
}
public virtual CalculateManager GetCalculateInstance()
{
Type calculateManagerType = Type.GetTypeFromProgID(calculateProgID);
CalculateManager calculate = (CalculateManager)Activator.CreateInstance(calculateManagerType);
return calculate;
}
}
它依赖于一个外部接口 CalculateManager, 这个dll以COM的形式注册,所以我们需要调用Activator.CreateInstance来初始化dll。
CalculateManager的代码如下:
1
2
3
4
5
6
7
public class CalculateManager
{
public virtual int Sum(int a, int b)
{
return a + b;
}
}
当我们测试DoCalculate时,首先要做的是隔离外部依赖,将CalculateManager的初始化放到一个单独的虚函数GetCalculateInstance中。然后使用mock做一个假的接口.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[TestClass()]
public class CalculateImpTests:CalculateImp
{
public override CalculateManager GetCalculateInstance()
{
var mock = new Mock<CalculateManager>();
mock.Setup(r => r.Sum(1, 2)).Returns(10);
return mock.Object;
}
[TestMethod()]
public void DoCalculateTest()
{
int result = base.DoCalculate();
Assert.AreEqual(result, 10);
}
}
如果接口的参数中有引用参数,则Unit test需要加Callback,测试也就更复杂一些。
1
2
3
4
5
6
7
public class CalculateManager
{
public virtual void Sum2(int a, int b, ref int amount)
{
amount = a + b;
}
}
使用ref参数来获取最终的结果
1
2
3
4
5
6
7
8
9
10
11
12
public class CalculateImp
{
public int DoCalculate2()
{
var cal = GetCalculateInstance();
int result = 0;
int a = 1;
int b = 2;
cal.Sum2(a, b, ref result);
return result;
}
}
如果对DoCalculate2写单元测试,则需要增加callback
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[TestClass()]
public class CalculateImpTests:CalculateImp
{
delegate void Sum2Callback(int a, int b, ref int amount);
public override CalculateManager GetCalculateInstance()
{
var mock = new Mock<CalculateManager>();
mock.Setup(m => m.Sum2(1, 2, ref It.Ref<int>.IsAny)) // match any value passed by-ref
.Callback(new Sum2Callback((int a, int b, ref int amount) =>
{
amount = 33;
}));
return mock.Object;
}
[TestMethod()]
public void DoCalculate2Test()
{
int result = base.DoCalculate2();
Assert.AreEqual(result, 33);
}
}
这是在声明mock的时候用到了ref It.Ref<int>.IsAny。
如果同时有引用参数和返回值的接口:
1
2
3
4
5
6
7
8
public class CalculateManager
{
public virtual bool Sum3(int a, int b, ref int amount)
{
amount = a + b;
return true;
}
}
调用sum3
1
2
3
4
5
6
7
8
9
10
11
12
public class CalculateImp
{
public int DoCalculate3()
{
var cal = GetCalculateInstance();
int result = 0;
int a = 1;
int b = 2;
cal.Sum3(a, b, ref result);
return result;
}
}
针对DoCalculate3,我们需要在callback的基础上加Returns
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[TestClass()]
public class CalculateImpTests:CalculateImp
{
delegate void Sum3Callback(int a, int b, ref int amount);
delegate bool Sum3Returns(int a, int b, ref int amount);
public override CalculateManager GetCalculateInstance()
{
var mock = new Mock<CalculateManager>();
mock.Setup(m => m.Sum3(1, 2, ref It.Ref<int>.IsAny))
.Callback(new Sum3Callback((int a, int b, ref int amount) =>
{
amount = 61;
}))
.Returns(new Sum3Returns((int a, int b, ref int amount) => true));
return mock.Object;
}
[TestMethod()]
public void DoCalculate3Test()
{
int result = base.DoCalculate3();
Assert.AreEqual(result, 61);
}
}
完整代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
public class CalculateManager
{
public virtual int Sum(int a, int b)
{
return a + b;
}
public virtual void Sum2(int a, int b, ref int amount)
{
amount = a + b;
}
public virtual bool Sum3(int a, int b, ref int amount)
{
amount = a + b;
return true;
}
}
public class CalculateImp
{
private const string calculateProgID = "Calculate.CalculateManager";
public int DoCalculate()
{
var cal = GetCalculateInstance();
return cal.Sum(1, 2);
}
public int DoCalculate2()
{
var cal = GetCalculateInstance();
int result = 0;
int a = 1;
int b = 2;
cal.Sum2(a, b, ref result);
return result;
}
public int DoCalculate3()
{
var cal = GetCalculateInstance();
int result = 0;
int a = 1;
int b = 2;
cal.Sum3(a, b, ref result);
return result;
}
public virtual CalculateManager GetCalculateInstance()
{
Type calculateManagerType = Type.GetTypeFromProgID(calculateProgID);
CalculateManager calculate = (CalculateManager)Activator.CreateInstance(calculateManagerType);
return calculate;
}
}
[TestClass()]
public class CalculateImpTests:CalculateImp
{
delegate void Sum2Callback(int a, int b, ref int amount);
delegate void Sum3Callback(int a, int b, ref int amount);
delegate bool Sum3Returns(int a, int b, ref int amount);
public override CalculateManager GetCalculateInstance()
{
var mock = new Mock<CalculateManager>();
mock.Setup(r => r.Sum(1, 2)).Returns(10);
mock.Setup(m => m.Sum2(1, 2, ref It.Ref<int>.IsAny))
.Callback(new Sum2Callback((int a, int b, ref int amount) =>
{
amount = 33;
}));
mock.Setup(m => m.Sum3(1, 2, ref It.Ref<int>.IsAny))
.Callback(new Sum3Callback((int a, int b, ref int amount) =>
{
amount = 61;
}))
.Returns(new Sum3Returns((int a, int b, ref int amount) => true));
return mock.Object;
}
[TestMethod()]
public void DoCalculateTest()
{
int result = base.DoCalculate();
Assert.AreEqual(result, 10);
}
[TestMethod()]
public void DoCalculate2Test()
{
int result = base.DoCalculate2();
Assert.AreEqual(result, 33);
}
[TestMethod()]
public void DoCalculate3Test()
{
int result = base.DoCalculate3();
Assert.AreEqual(result, 61);
}
}
Reference