2014-11-25 69 views
1

我測試了以下控制器初始化:c。與私有構造#mock對象,通過靜態工廠方法

public class MyController : ApiController 
{ 
    private readonly IUserManager _users; 
    private int num = 0; 

    public MyController(IUserManager users) 
    { 
     _users = users; 
    } 

    public User GetByCredentials(string username, string pass) 
    { 
     var account = new Account(username, pass);   
     User u = _users.GetByCredentials(num, account); 
     return u; 
    } 

我想嘲笑IUserManager.GetByCredentials方法,因爲我只想看到MyController.GetByCredentials方法按預期工作。問題是,用戶類不能直接實例化,所以我不能嘲笑一個User對象,因爲構造函數是私有的:

public class User 
{ 
    // private attributes here 

    protected User() 
    { 
     // do a lot of stuff here, call other objects to initialize attributes 
    } 

    private User(int num, string id, string userid, Account account) 
     : this() 
    { 
     // do other stuff here with the params 
    } 


    public static User CreateUser(int num, string id, string userid, Account account) 
    { 
     return new User(num, id, userid, account); 
    } 
    // and so on 

} 

我現在用的起訂量框架,但我願意接受不同的解決方案。在這種情況下,我寧願避免創建測試數據,因爲它依賴於數據庫的初始化,服務器等 - 然後它不再是單元測試。你有過這樣的問題嗎?你如何解決?謝謝。

回答

3

您不需要模擬User - 您可以使用真實User類。你只需要模擬(或僞造)IUserManager。您的模擬/假冒IUserManager可以使用User.CreateUser創建要返回到控制器的User對象。

除非User類本身「知道」數據庫,否則應該沒問題。抵制模擬的誘惑 - 您只需要擺脫使編寫測試變得困難的依賴關係。

現在你寫你的User私有構造「做了很多的東西」 - 如果到達的太多,你應該重新設計User所以它更簡單...您的用戶經理應該可能是負責一些那裏正在進行的工作。

雖然這是一個過於簡單化的世界完全分裂成服務(如用戶管理器)和愚蠢的數據對象(如用戶),它使事情變得更加簡單,如果設計合理自然拆分本身在這方面。

+0

感謝您的回答。我已經爲前一個添加了一條評論,我想唯一的辦法就是重構該代碼......該死的:S – Markon 2014-11-25 15:51:08

+1

@Markon:基本上,當你有無法測試的代碼時,正確的路要走*是*重構它。我的意思是,你可以創建一個'IUser'接口,然後模擬*,但它聽起來像'User'中的混亂應該被解決。 – 2014-11-25 15:52:12

+0

好的,謝謝 - 這是我想要的確認:) – Markon 2014-11-25 16:09:29

3

爲了測試你MyController,你會被嘲笑IUserManagerUser這樣你就可以做這樣的事情:

var mockUserManager = new Mock<IUserManager>(); 

// Configure the User to be returned by calling GetByCredentials 
mockUserManager 
    .Setup(x => x.GetByCredentials(It.IsAny<int>(), It.IsAny<Account>()) 
    .Returns(User.CreateUser(1, "foo", "username", new Account()); 

var controller = new MyController(mockUserManager.Object); 

var user = controller.GetByCredentials("username", "password"); 

Assert.NotNull(user); 

mockUserManager.Verify(x => x.GetByCredentials(It.IsAny<int>(), It.IsAny<Account>(), Times.Once()); 

模擬/假冒對象的一點是要避免數據庫/ web服務調用。您的AccountUser類應該是poco類 - 例如只包含作用於自身的方法和屬性,不包含數據庫或Web服務調用等,因此它們實際上不需要嘲笑。

+0

是的,這正是我所做的 - 嘲笑IUserManager,但不幸的是User.CreateUser方法正在調用數據庫/服務器來獲取配置等等。是的,我知道,這是一種糟糕的設計,這必須通過在用戶上使用依賴注入來重構(這樣我才能模擬數據庫和其他一切),但我想知道是否有一種「更容易」的方式。 – Markon 2014-11-25 15:49:54

+0

你可以從你從IUserManager返回的User類創建一個IUser接口,然後也可以模擬它,但是如上所述,最好從靜態方法中刪除數據庫代碼。 – 2014-11-25 16:25:36