使用 Moq 的 Elasticsearch Nest 单元测试:无法模拟混凝土 class
Elasticsearch Nest unit tests with Moq : fail to mock concrete class
我需要在我的存储库中使用的 Nest.IElasticClient
接口中模拟一些方法和属性。
例如,我需要将 Indices.Exists()
方法存根到 return 具有 Exists
属性 return 的 ExistsResponse
为真。
问题是具体的 class 没有接口实现,在 Exists 属性 上也没有 setter,并且在 Nest 库中也没有声明为虚拟的:
public class ExistsResponse : ResponseBase
{
public ExistsResponse();
public bool Exists { get; }
}
public ExistsResponse Exists(Indices index, Func<IndexExistsDescriptor, IIndexExistsRequest> selector = null);
因此,对于模拟,我尝试在具体 class 上设置 属性,但以下所有方法都失败了,我不知道该怎么做 ...
/* Fail with exception :
System.NotSupportedException : Unsupported expression: x => x.Exists
Non-overridable members (here: ExistsResponse.get_Exists) may not be used in setup / verification expressions.
*/
var mock1 = new Mock<ExistsResponse>();
obj.SetupGet(f => f.Exists).Returns(true);
/* Fail with exception :
System.NotSupportedException : Unsupported expression: f => f.Exists
Non-overridable members (here: ExistsResponse.get_Exists) may not be used in setup / verification expressions.
*/
var mock2 = Mock.Of<ExistsResponse>(x => x.Exists == true);
/* Fail with exception :
System.ArgumentException : Property set method not found.
*/
var mock3 = new ExistsResponse();
var property = typeof(ExistsResponse).GetProperty("Exists", BindingFlags.Public | BindingFlags.Instance);
property.SetValue(mock3, true);
/* Fail with exception :
System.NullReferenceException (setter is null)
*/
var mock4 = new ExistsResponse();
var setter = property.GetSetMethod(true);
setter.Invoke(mock4, new object[] { true });
// My Mock on the Indices.Exists method
var elasticMock = new Mock<IElasticClient>();
elasticMock
.Setup(x => x.Indices.Exists(It.IsAny<string>(), null))
.Returns(/*** my stubbed object here ***/); // <== how to stub the concrete class to return a ExistsResponse.Exists = true ?
我的库:
Nest 7.12.1
Moq 4.15.2
XUnit 2.4.1
.Net 5
感谢您的帮助
模拟你不拥有的东西通常不好,但如果你想模拟弹性客户端响应,最好的方法是使用 InMemorryConnection,你可以像这样用 InMemorryConnection 实例化一个新的 elasticClient:
InMemory 弹性客户端:
var connection = new InMemoryConnection();
var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var settings = new ConnectionSettings(connectionPool, connection);
var client = new ElasticClient(settings);
然后您将需要伪造响应并要求客户 return 如下所示:
var response = new
{
took = 1,
timed_out = false,
_shards = new
{
total = 2,
successful = 2,
failed = 0
},
hits = new
{
total = new { value = 25 },
max_score = 1.0,
hits = Enumerable.Range(1, 25).Select(i => (object)new
{
_index = "project",
_type = "project",
_id = $"Project {i}",
_score = 1.0,
_source = new { name = $"Project {i}" }
}).ToArray()
}
};
var responseBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(response));
var connection = new InMemoryConnection(responseBytes, 200);
var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var settings = new ConnectionSettings(connectionPool, connection).DefaultIndex("project");
var client = new ElasticClient(settings);
var searchResponse = client.Search<Project>(s => s.MatchAll());
您可以找到有关 InMemoryConnection 的更多信息here。
如果您想正确遵循不要嘲笑您不拥有的东西,您需要执行以下步骤:
- 包装
ElasticClient
和所有Request
和Responses
的 ElasticClient 在这个库中,然后你可以轻松地模拟你需要的东西。
- 您应该使用集成测试来测试您的包装器 class,以确保您的包装器 class 按预期工作。
更新:
为了模拟 Indices.Exists
你只需要发送 200 OK 所以你只需要这样做:
var connection = new InMemoryConnection(responseBytes, 200);
我需要在我的存储库中使用的 Nest.IElasticClient
接口中模拟一些方法和属性。
例如,我需要将 Indices.Exists()
方法存根到 return 具有 Exists
属性 return 的 ExistsResponse
为真。
问题是具体的 class 没有接口实现,在 Exists 属性 上也没有 setter,并且在 Nest 库中也没有声明为虚拟的:
public class ExistsResponse : ResponseBase
{
public ExistsResponse();
public bool Exists { get; }
}
public ExistsResponse Exists(Indices index, Func<IndexExistsDescriptor, IIndexExistsRequest> selector = null);
因此,对于模拟,我尝试在具体 class 上设置 属性,但以下所有方法都失败了,我不知道该怎么做 ...
/* Fail with exception :
System.NotSupportedException : Unsupported expression: x => x.Exists
Non-overridable members (here: ExistsResponse.get_Exists) may not be used in setup / verification expressions.
*/
var mock1 = new Mock<ExistsResponse>();
obj.SetupGet(f => f.Exists).Returns(true);
/* Fail with exception :
System.NotSupportedException : Unsupported expression: f => f.Exists
Non-overridable members (here: ExistsResponse.get_Exists) may not be used in setup / verification expressions.
*/
var mock2 = Mock.Of<ExistsResponse>(x => x.Exists == true);
/* Fail with exception :
System.ArgumentException : Property set method not found.
*/
var mock3 = new ExistsResponse();
var property = typeof(ExistsResponse).GetProperty("Exists", BindingFlags.Public | BindingFlags.Instance);
property.SetValue(mock3, true);
/* Fail with exception :
System.NullReferenceException (setter is null)
*/
var mock4 = new ExistsResponse();
var setter = property.GetSetMethod(true);
setter.Invoke(mock4, new object[] { true });
// My Mock on the Indices.Exists method
var elasticMock = new Mock<IElasticClient>();
elasticMock
.Setup(x => x.Indices.Exists(It.IsAny<string>(), null))
.Returns(/*** my stubbed object here ***/); // <== how to stub the concrete class to return a ExistsResponse.Exists = true ?
我的库:
Nest 7.12.1
Moq 4.15.2
XUnit 2.4.1
.Net 5
感谢您的帮助
模拟你不拥有的东西通常不好,但如果你想模拟弹性客户端响应,最好的方法是使用 InMemorryConnection,你可以像这样用 InMemorryConnection 实例化一个新的 elasticClient:
InMemory 弹性客户端:
var connection = new InMemoryConnection();
var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var settings = new ConnectionSettings(connectionPool, connection);
var client = new ElasticClient(settings);
然后您将需要伪造响应并要求客户 return 如下所示:
var response = new
{
took = 1,
timed_out = false,
_shards = new
{
total = 2,
successful = 2,
failed = 0
},
hits = new
{
total = new { value = 25 },
max_score = 1.0,
hits = Enumerable.Range(1, 25).Select(i => (object)new
{
_index = "project",
_type = "project",
_id = $"Project {i}",
_score = 1.0,
_source = new { name = $"Project {i}" }
}).ToArray()
}
};
var responseBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(response));
var connection = new InMemoryConnection(responseBytes, 200);
var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var settings = new ConnectionSettings(connectionPool, connection).DefaultIndex("project");
var client = new ElasticClient(settings);
var searchResponse = client.Search<Project>(s => s.MatchAll());
您可以找到有关 InMemoryConnection 的更多信息here。
如果您想正确遵循不要嘲笑您不拥有的东西,您需要执行以下步骤:
- 包装
ElasticClient
和所有Request
和Responses
的 ElasticClient 在这个库中,然后你可以轻松地模拟你需要的东西。 - 您应该使用集成测试来测试您的包装器 class,以确保您的包装器 class 按预期工作。
更新:
为了模拟 Indices.Exists
你只需要发送 200 OK 所以你只需要这样做:
var connection = new InMemoryConnection(responseBytes, 200);