Moq:如何模拟不可见的类?

发布于 2024-09-16 06:12:23 字数 940 浏览 4 评论 0 原文

我有以下简化的代码来描述我的问题:

public interface IMyUser
{
    int Id { get; set; }
    string Name { get; set; }
}

它在数据访问层中使用,如下所示:

public interface IData
{
    T GetUserById<T>(int id) where T : IMyUser, new();
}

userlogic 类定义如下:

public class UserLogic
{
    private IData da;

    public UserLogic(IData da)
    {
        this.da = da;
    }

    public IMyUser GetMyUserById(int id)
    {
        return da.GetUserById<MyUser>(id);
    }
}

userlogic 使用仅在内部可见的 MyUSer 类。

我想使用 Moq 来模拟对数据访问层的调用。但因为我无法从我的单元测试代码(按设计)访问 MyUser 类,我不知道如何设置最小起订量?

起订量代码应该类似于:

var data = new Mock<IData>();
data.Setup(d => d.GetUserById<MyUser ???>(1)).Returns(???);

var logic = new UserLogic(data.Object);
var result = logic.GetMyUserById(1);

如何解决这个问题?

I've the following simplified code which describes my problem:

public interface IMyUser
{
    int Id { get; set; }
    string Name { get; set; }
}

Which is used in the dataccess layer like this:

public interface IData
{
    T GetUserById<T>(int id) where T : IMyUser, new();
}

The userlogic class is defined as follows:

public class UserLogic
{
    private IData da;

    public UserLogic(IData da)
    {
        this.da = da;
    }

    public IMyUser GetMyUserById(int id)
    {
        return da.GetUserById<MyUser>(id);
    }
}

The userlogic uses a MyUSer class which is only visible internally.

I want to use Moq to mock the call to the dataaccess layer. But becuase I cannot access the MyUser class from my unit test code (which is as designed) , I don't know how to setup moq?

The Moq code should be something like:

var data = new Mock<IData>();
data.Setup(d => d.GetUserById<MyUser ???>(1)).Returns(???);

var logic = new UserLogic(data.Object);
var result = logic.GetMyUserById(1);

How to solve this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

蓝眼泪 2024-09-23 06:12:23

让我扩展一下 Sjoerd 的答案。您面临的问题是由于无法从测试程序集中访问 MyUser 类型。使用 InternalsVisibleTo 程序集属性可以轻松解决该问题。

不过,我建议重新考虑您的设计并摆脱 IMyUser 接口,而只使用 MyUser 类(应该是公共的)。通常,您将服务放在接口后面,而不是实体后面。提供 IMyUser 的多种实现有什么好的理由吗?

看看这个实现有多干净:

public interface IData
{
    MyUser GetUserById(int id);
}

public class UserLogic
{
    private IData da;

    public UserLogic(IData da)
    {
        this.da = da;
    }

    public MyUser GetMyUserById(int id)
    {
        return da.GetUserById(id);
    }
}

internal class MyUser {
    int Id { get; set; }
    string Name { get; set; }
}

如果您坚持使用 IMyUser 接口及其内部实现,还有另一种解决方案。如果我正确推断 IData.GetUserById 的内容,您现有的解决方案将如下所示:

public class UserData : IData {
    T GetUserById<T>(int id) where T : IMyUser, new(){
       T returned = new T();
       //fill in properties
       returned.Name = "test";
       return returned;
    }
}

上面的代码稍微违反了 SRP(警告,PDF) 并混合了两个职责 - 从持久存储中检索实体并创建实体的实例。不仅如此,它还将创建责任放在了界面上,这就更糟糕了。

使用 Abstract Factory依赖注入(PDF) 模式将带来更加简洁的设计,不会遇到与以前相同的问题。

public interface IMyUserFactory {
    IMyUser Create();
}

public interface IData
{
    IMyUser GetUserById(int id);
}

internal MyUserFactory : IMyUserFactory {
   public IMyUser Create() {return new MyUser();}
}

internal class UserData : IData {

    IMyUserFactory m_factory;
    public UserData(IMyUserFactory factory) {
       m_factory = factory;
    }

    public IMyUser GetUserById(int id) {
       IMyUser returned = m_factory.Create();
       //fill in properties
       returned.Name = "test";
       return returned;
    }
}

//and finally UserLogic class
public class UserLogic
{
    private IData da;

    public UserLogic(IData da)
    {
        this.da = da;
    }

    public IMyUser GetMyUserById(int id)
    {
        return da.GetUserById(id);
    }
}

//The test then becomes trivial
[TestMethod]
public void Test() {
  var data = new Mock<IData>();
  data.Setup(d => d.GetUserById(1)).Returns(new Mock<IMyUser>().Object);

  var logic = new UserLogic(data.Object);
  var result = logic.GetMyUserById(1);
}

Let me just expand on Sjoerd's answer. The problem you are facing is due to not being able to access MyUser type from the test assembly. That problem is easily fixed with InternalsVisibleTo assembly attribute.

I would however recommend to rethink your design and get rid of IMyUser interface and instead just use MyUser class (which should be public). Normally you put services behind interfaces, not entities. Are there any good reasons for providing multiple implementations of IMyUser?

Have a look at how much cleaner this implementation is:

public interface IData
{
    MyUser GetUserById(int id);
}

public class UserLogic
{
    private IData da;

    public UserLogic(IData da)
    {
        this.da = da;
    }

    public MyUser GetMyUserById(int id)
    {
        return da.GetUserById(id);
    }
}

internal class MyUser {
    int Id { get; set; }
    string Name { get; set; }
}

There is another solution, if you insist on having IMyUser interface and its internal implementation. Your existing solution, if I infer the contents of IData.GetUserById<T> correctly, goes something like this:

public class UserData : IData {
    T GetUserById<T>(int id) where T : IMyUser, new(){
       T returned = new T();
       //fill in properties
       returned.Name = "test";
       return returned;
    }
}

The above code is a slight violation of SRP(warning, PDF) and mixes two responsibilities - retrieving an entity from persistent storage and creating an instance of the entity. Not only that, it also puts the creation responsibility on the interface, which is even worse.

Decoupling those responsibilities using Abstract Factory and Dependency Injection(PDF) patterns will lead to much cleaner design that does not suffer from the same problem as before.

public interface IMyUserFactory {
    IMyUser Create();
}

public interface IData
{
    IMyUser GetUserById(int id);
}

internal MyUserFactory : IMyUserFactory {
   public IMyUser Create() {return new MyUser();}
}

internal class UserData : IData {

    IMyUserFactory m_factory;
    public UserData(IMyUserFactory factory) {
       m_factory = factory;
    }

    public IMyUser GetUserById(int id) {
       IMyUser returned = m_factory.Create();
       //fill in properties
       returned.Name = "test";
       return returned;
    }
}

//and finally UserLogic class
public class UserLogic
{
    private IData da;

    public UserLogic(IData da)
    {
        this.da = da;
    }

    public IMyUser GetMyUserById(int id)
    {
        return da.GetUserById(id);
    }
}

//The test then becomes trivial
[TestMethod]
public void Test() {
  var data = new Mock<IData>();
  data.Setup(d => d.GetUserById(1)).Returns(new Mock<IMyUser>().Object);

  var logic = new UserLogic(data.Object);
  var result = logic.GetMyUserById(1);
}
迷你仙 2024-09-23 06:12:23

你不能

da.GetUserById<IMyUser>(id);

da.GetUserById<MyUser>(id);

Can't you use

da.GetUserById<IMyUser>(id);

instead of

da.GetUserById<MyUser>(id);
§普罗旺斯的薰衣草 2024-09-23 06:12:23

如果我想隐藏功能但让它可测试,我会将函数声明为 internal,然后在文件顶部添加 [ assembly: InternalsVisibleTo("MyAssemblyName" )] 属性,其中 MyAssemblyName 是您要授予访问权限的单元测试程序集。谢谢 Stef 指出我之前的错误。

If I want to hide functionality but let it be testable, I'll declare the functions as internal, and then at the top of the file I add the [assembly: InternalsVisibleTo("MyAssemblyName")] attribute, where MyAssemblyName is the unit test assembly that you want to grant access to. Thanks, Stef, for pointing out my previous mistake.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文