我如何使用 Moq 来模拟一个接口,该接口具有将具体 类 作为参数的方法?
How do I use Moq to mock an interface that has methods that take concrete classes as parameters?
- 我有这种情况:一个接口有 2 个方法
- 这 2 种方法接受请求和 return 响应
- 方法包含内部功能(检查权限并验证请求并使用 entity framework 从数据库获取数据)。
- 但我想测试方法而不仅仅是界面。
- 我已经成功测试了界面,但现在我想进入方法并在其中进行测试。
代码示例:
public interface IMyInterface
{
[OperationContract]
responseObject GetData(Service<RequestObject> request);
}
public class MyConcreteClass : IMyInterface
{
public responseObject GetData(Service<RequestObject> request)
{
CheckForNull(request);
ValidateMethod(request);
//connect to db
using(var context = new contextEntity)
{
//get data
}
}
}
现在,我想测试检查空值、权限和数据访问,可以吗?还是我必须从内部方法中提取接口?
PS,这是为了我的单元测试。我正在尝试消除外部依赖性。
请详细说明
不应直接需要单元测试私有方法,只能通过 public 方法间接使用。如果您认为您测试的 public 方法不够精确,可能是因为该方法和 class 已经太复杂了。
在这种情况下,请考虑在新代码所在的位置创建一个或多个新的 classes。这样你就可以通过 public 方法对你的代码进行单元测试。额外的好处是您的代码在 Single responsibility principle.
方面可能更好
嘲笑的原因是你可以控制行为;在这种情况下,我会冒险猜测 Service<RequestObject>
实际上没有任何行为,因此它实际上 不需要 嘲笑。所以它可能可以在任何测试中按原样通过。
但是,如果它 确实 有行为,并且该行为跨越架构边界(假设在 GetData
方法中,您正在 request
将进行网络调用或访问文件系统等的参数)然后你需要模拟它。但是你可以很容易地做到这一点:
public interface IService<RequestObject>
{
//put method and property signatures of Service<RequestObject> here
}
public class ServiceObject:Service<RequestObject>, IService<RequestObject>
{
public ServiceObject(RequestObject request): base(request){
//or however the Service<Request> object is instantiated.
};
}
然后把GetData
改成IService<RequestObject>
;现在您的调用代码可以实例化 ServiceObject
来代替 Service 并将其传递给 GetData
方法。另外,您可以根据需要在界面上模拟任何方法。显然,如果您不控制调用代码,那将是一个问题,因为编写该代码的人需要这样做,但这是您需要与他们进行的对话:)
在测试内部操作方面,您需要了解如何抽象 GetData
方法使用的任何依赖行为 - 例如,contextEntity
、CheckForNull
, ValidateMethod
, 等等——所有这些都是被提取到它们自己的抽象中的候选对象,并且 injected 作为依赖项被提取到 MyConcreteClass
中,例如:
public class MyConcreteClass: IMyInterface
{
readonly INullChecker _nullChecker;
readonly IValidator _validator;
readonly IContextEntity _context;
public MyConcreteClass(INullChecker nullChecker, IValidator validator, IContextEntity _context)
{
_nullChecker = nullChecker;
_validator = validator;
_context=context;
}
public responseObject GetData(Service<RequestObject> request)
{
_nullChecker.Check(request)//**;
_validator.Validate(request);
var result = _context.DoSomethingWith(request);
return result;
}
}
现在您可以为 MyConcreteClass
编写测试并使用依赖项的模拟实现,以确保 GetData 方法正确使用它们。
**我猜这可以用一个简单的 if request==null throw new ArgumentNullException()
代替,它更干净、更简单。
- 我有这种情况:一个接口有 2 个方法
- 这 2 种方法接受请求和 return 响应
- 方法包含内部功能(检查权限并验证请求并使用 entity framework 从数据库获取数据)。
- 但我想测试方法而不仅仅是界面。
- 我已经成功测试了界面,但现在我想进入方法并在其中进行测试。
代码示例:
public interface IMyInterface
{
[OperationContract]
responseObject GetData(Service<RequestObject> request);
}
public class MyConcreteClass : IMyInterface
{
public responseObject GetData(Service<RequestObject> request)
{
CheckForNull(request);
ValidateMethod(request);
//connect to db
using(var context = new contextEntity)
{
//get data
}
}
}
现在,我想测试检查空值、权限和数据访问,可以吗?还是我必须从内部方法中提取接口?
PS,这是为了我的单元测试。我正在尝试消除外部依赖性。 请详细说明
不应直接需要单元测试私有方法,只能通过 public 方法间接使用。如果您认为您测试的 public 方法不够精确,可能是因为该方法和 class 已经太复杂了。
在这种情况下,请考虑在新代码所在的位置创建一个或多个新的 classes。这样你就可以通过 public 方法对你的代码进行单元测试。额外的好处是您的代码在 Single responsibility principle.
方面可能更好嘲笑的原因是你可以控制行为;在这种情况下,我会冒险猜测 Service<RequestObject>
实际上没有任何行为,因此它实际上 不需要 嘲笑。所以它可能可以在任何测试中按原样通过。
但是,如果它 确实 有行为,并且该行为跨越架构边界(假设在 GetData
方法中,您正在 request
将进行网络调用或访问文件系统等的参数)然后你需要模拟它。但是你可以很容易地做到这一点:
public interface IService<RequestObject>
{
//put method and property signatures of Service<RequestObject> here
}
public class ServiceObject:Service<RequestObject>, IService<RequestObject>
{
public ServiceObject(RequestObject request): base(request){
//or however the Service<Request> object is instantiated.
};
}
然后把GetData
改成IService<RequestObject>
;现在您的调用代码可以实例化 ServiceObject
来代替 Service 并将其传递给 GetData
方法。另外,您可以根据需要在界面上模拟任何方法。显然,如果您不控制调用代码,那将是一个问题,因为编写该代码的人需要这样做,但这是您需要与他们进行的对话:)
在测试内部操作方面,您需要了解如何抽象 GetData
方法使用的任何依赖行为 - 例如,contextEntity
、CheckForNull
, ValidateMethod
, 等等——所有这些都是被提取到它们自己的抽象中的候选对象,并且 injected 作为依赖项被提取到 MyConcreteClass
中,例如:
public class MyConcreteClass: IMyInterface
{
readonly INullChecker _nullChecker;
readonly IValidator _validator;
readonly IContextEntity _context;
public MyConcreteClass(INullChecker nullChecker, IValidator validator, IContextEntity _context)
{
_nullChecker = nullChecker;
_validator = validator;
_context=context;
}
public responseObject GetData(Service<RequestObject> request)
{
_nullChecker.Check(request)//**;
_validator.Validate(request);
var result = _context.DoSomethingWith(request);
return result;
}
}
现在您可以为 MyConcreteClass
编写测试并使用依赖项的模拟实现,以确保 GetData 方法正确使用它们。
**我猜这可以用一个简单的 if request==null throw new ArgumentNullException()
代替,它更干净、更简单。