1 基礎概念
集中精力開發業務邏輯部分,而不想在數據層上花費太多時間,這時,可以通過Mock對象來模擬數據層,而不必去為數據連接,CRUD,Mapping等等去做太多的事,而又可以使業務測試可以進行下去
2 教程及下載地址
http://www.codethinked.com/beginning-mocking-with-moq-3-part-1
http://www.cnblogs.com/jams742003/archive/2010/03/02/1676215.html
3 一個Demo
//接口
public interface ICustomer
{
void AddCall();
string GetCall();
string GetCall(string strUser);
}
var customer = new Mock<ICustomer>(); //建立Mock對象
//設置mock調用行為
customer.Setup(p=>p.AddCall());
customer.Setup(p => p.GetCall()).Returns("phone:89898789");
customer.Setup(p => p.GetCall("Tom")).Returns("Hello");
//使用mock調用方法
customer.Object.AddCall();
Assert.AreEqual("phone:89898789", customer.Object.GetCall());
Assert.AreEqual("Hello", customer.Object.GetCall("Tom"));
4 功能點介紹
4.1 帶有引用或輸出參數的方法
string GetAddress(string strUser, out string Address);
string GetFamilyCall(ref string strUser);
var customer = new Mock<ICustomer>();
var outString="oo";
customer.Setup(p => p.GetAddress("", out outString)).Returns("shijiazhuang");
customer.Setup(p => p.GetFamilyCall(ref outString)).Returns("xx");
4.2 調用方法時拋出異常
方法:void ShowException(string str);
測試:
var customer = new Mock<ICustomer>();
customer.Setup(p => p.ShowException(string.Empty))
.Throws(new Exception("參數不能為空!"));
customer.Object.ShowException("");
4.3 調用時賦值
方法:void AddCall();
Mock測試:
var customer = new Mock<ICustomer>();
int iCount = 0;
customer.Setup(p => p.AddCall()).Callback(()=>iCount++);
Assert.AreEqual(0, iCount);
customer.Object.AddCall();
Assert.AreEqual(1, iCount);
customer.Object.AddCall();
Assert.AreEqual(2, iCount);
customer.Object.AddCall();
Assert.AreEqual(3, iCount);
4.4 Is<T>:匹配確定的給定類型
customer.Setup(x => x.SelfMatch(It.Is<int>(i => i % 2 == 0))).Returns("1");
方法SelfMatch接受int型參數,當參數為偶數時,才返回字符串1。
4.5 IsAny<T>:匹配給定的任何值
customer.Setup(p => p.SelfMatch(It.IsAny<int>())).Returns((int k) => "任何數:" + k);
方法SelfMatch接受int型,且任何int型參數都可以,然后返回:"任何數:" + k。
這里說明一下Returns方法:
Returns(Func<TResult>)
Returns<T>(Func<T,TResult>)
Returns<T1,T2>(Func<T1,T2,TResult>)
Returns<T1,T2,T3>(Func<T1,T2,T3,TResult>)
Returns<T1,T2,T3,T4>(Func<T1,T2,T3,T4,TResult>)
Returns(TResult)
4.6 IsInRange<T>:匹配給定類型的范圍
customer.Setup(p => p.SelfMatch(It.IsInRange<int>(0, 10, Range.Inclusive)))
.Returns("10以內的數");
方法SelfMatch接受int型,且當范圍在[0,10]時,才返回10以內的數
其中,這個方法,帶有一個包含與排除開關。
4.7 IsRegex<T>:正則匹配
customer.Setup(p => p.ShowException(It.IsRegex(@"^\d+$")))
.Throws(new Exception("不能是數字"));
4.8 設置屬性
public class Customer
{
public virtual int Unid { get; set; }
public virtual string Name { get; set; }
}
測試
var customer = new Mock<Customer>();
customer.Setup(p => p.Name).Returns("Tom");
customer.SetupProperty(p => p.Name, "tt");
4.9 Callbacks回調函數
當執行某方法時,調用其內部輸入的(Action)委托
看它的5種重載:
Callback(Action)
Callback<T>(Action<T>)
Callback<T1, T2>(Action<T1, T2>)
Callback<T1, T2, T3>(Action<T1, T2, T3>)
Callback<T1, T2, T3, T4>(Action<T1, T2, T3, T4>)
這個方法調用其內部輸入的Action委托,Aciton<T>有5種重載,所以這里的Callbacks有5種重載。
以第二個為例:
它的定義為:
ICallbackResult Callback<T>(Action<T> action)
這個表示一個輸入參數,
var customer = new Mock<ICustomer>();
customer.Setup(p => p.GetCall(It.IsAny<string>()))
.Returns("方法調用")
.Callback((string s)=>Console.WriteLine("ok"+s));
customer.Object.GetCall("x");
4.10 Verify 驗證
用於測試mock對象的方法或屬性是否被調用執行。當不需要測試結果時用到
重載很多:
Verify()
Verify(Expression<Action<T>>)
Verify<TResult>(Expression<Func<T, TResult>>)
Verify(Expression<Action<T>>, Times)
Verify(Expression<Action<T>>, String)
Verify<TResult>(Expression<Func<T, TResult>>, Times)
Verify<TResult>(Expression<Func<T, TResult>>, String)
Verify(Expression<Action<T>>, Times, String)
Verify<TResult>(Expression<Func<T, TResult>>, Times, String)
用其中三個舉例
第一個:Verify(),定義為:
public void Verify()
測試
public void TestVerify()
{
var customer = new Mock<ICustomer>();
customer.Setup(p => p.GetCall(It.IsAny<string>()))
.Returns("方法調用")
.Callback((string s) => Console.WriteLine("ok" + s))
.Verifiable();
customer.Object.GetCall("調用了!");
customer.Verify();
}
把Mock對象中的GetCall方法設置為可證實的(Verifiable()),
如果不調用粗體部分語句,那么customer.Verify()執行就不能通過。這個方法很有用。
第二個:Verify(Expression<Action<T>>),定義為:
public void Verify(Expression<Action<T>> expression)
customer.Verify(p => p.GetCall("call"));
如果沒有調用且輸入call字串的參數,則失敗。
第三個:Verify(Expression<Action<T>>, Times, String)
定義:
public void Verify( Expression<Action<T>> expression,
Times times,
string failMessage
)
這個比上一個多了兩個參數,一個用於表示調用次數相關描述,一個用於失敗的時打印信息
customer.Setup(p => p.GetCall(It.IsAny<string>()))
.Returns("方法調用")
.Callback((string s) => Console.WriteLine("ok" + s))
.Verifiable();
customer.Object.GetCall("call");
customer.Object.GetCall("call");
customer.Verify(p => p.GetCall("call"),
Times.AtLeast(2),"至少應被調用2次");
當GetCall方法被調用最少2次(且參數為call)時,測試成功。
方法中Times是個Times類類型,它有多個方法:
AtLeast
AtLeastOnce
AtMost
AtMostOnce
Between
Exactly
Never
Once
可以從語義上理解它們各自是什么意思,例如:AtLeast的定義為:
public static Times AtLeast(
int callCount
)
4.11 VerifyAll 驗證
在使用Verify方法時,只有被標記為可證實的(.Verifiable())的才可以驗證。
但VerifyAll會驗證所有的調用:
customer.Setup(p => p.GetCall(It.IsAny<string>()))
.Returns("方法調用")
.Callback((string s) => Console.WriteLine("ok" + s));
customer.Object.GetCall("call");
customer.VerifyAll();
4.12 Mock<T> Class
public class Mock<T> : Mock
where T : class
這的構造方法:
Mock<T>()
Mock<T>(MockBehavior)
Mock<T>(array<Object>[])
Mock<T>(MockBehavior, array<Object>[])
Mock的泛型實現類,它有很多方法和屬性。這里一一列舉。
(一)方法
(1)As<TInterface>方法
為mock添加接口實現(mock),可以給它指定設置。
在mock對象的屬性(或方法)首次使用之前才有效。且,參數只能是接口。
定義:
public virtual Mock<TInterface> As<TInterface>()
where TInterface : class
示例:
兩個接口:
其中的Icustomer接口還是前幾篇中用到的,這里添加一個Iorder接口:
public interface IOrder
{
string ShowTitle(string str);
}
Mock測試:
var customer = new Mock<ICustomer>();
customer.Setup(p => p.GetCall()).Returns("方法調用");
customer.Object.GetCall();
var order=customer.As<IOrder>();
order.Setup(p => p.ShowTitle(It.IsAny<string>())).Returns("ok");
Assert.AreEqual("ok",order.Object.ShowTitle(""));
這個將出現異常,因為在As之前,已經對GetCall進行了調用。
(2)SetUp方法
為模擬的對象中的方法指定設置,它有兩個重載:
Setup(Expression<Action<T>>)
Setup<TResult>(Expression<Func<T,TResult>>)
從兩個委托可以知道,這兩個一個是為沒有返回值的方法設置,一個是對有返回值的方法設置
public void TestSetUp()
{
var customer = new Mock<ICustomer>();
customer.Setup(p => p.AddCall())
.Callback(()=>Console.WriteLine("沒有返回值"));
customer.Setup(p => p.GetCall(It.IsAny<string>()))
.Returns("ok")
.Callback((string q) => Console.WriteLine("有返回值"));
customer.Object.AddCall();
customer.Object.GetCall("");
}
