Unit tests : Make all concrete class mockable
See original GitHub issueHi,
I need to mock some methods and properties in Nest.IElasticClient
interface that is used in my repo. As example I need to stub the Indices.Exists()
method to return an ExistsResponse
having an Exists
property returning true
.
The problem is that the concrete class has no interface implementation, nor setter on the Exists property, and is not declared virtual neither in Nest lib:
public class ExistsResponse : ResponseBase
{
public ExistsResponse();
public bool Exists { get; }
}
public ExistsResponse Exists(Indices index, Func<IndexExistsDescriptor, IIndexExistsRequest> selector = null);
So for the mocking I tried to set the property anyway on the concrete class, but it failed with all the following methods, I have no idea on how to do …
/* 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 ?
var repositoryMock = new Mock<GeocodedCityElkRepository>(elasticMock.Object, new NullLogger<GeocodedCityElkRepository>());
repositoryMock
.Setup(x => x.Get(It.IsAny<int>(), It.IsAny<Func<QueryContainerDescriptor<GeocodedCity>, QueryContainer>>()))
.Returns(Task.FromResult(new List<GeocodedCity>().AsReadOnly() as IReadOnlyCollection<GeocodedCity>));
// Act
var exception = await Assert.ThrowsAsync<GeocodingException>(() =>
repositoryMock.Object.GetDensity("11111", "city", "FR"));
Well unless I missed something, I’m stuck …
Describe the solution you’d like Allow to mock concrete class (making property virtual) or at least provide interface for returned objects like ExistsResponse.
public interface IExistsResponse
{
bool Exists { get; }
}
[DataContract]
public class ExistsResponse : ResponseBase, IExistsResponse
{
public ExistsResponse();
public bool Exists { get; }
}
Describe alternatives you’ve considered Do not test my repository …
Additional context
My libs :
Nest 7.12.1
Moq 4.15.2
XUnit 2.4.1
.Net 5
Here is a subset of my repo
Issue Analytics
- State:
- Created 2 years ago
- Comments:6 (2 by maintainers)
Top GitHub Comments
@KPGDL Steve Gordon gives the solution in this same topic : https://github.com/elastic/elasticsearch-net/issues/5714#issuecomment-855940949
The key point is StatusCode (404 or 200 as default) depending of what you intend to mock. I didn’t want to test if the
client.Indices.Exists("xxx")
returnedtrue
orfalse
, but as it is called from my ctor, I needed to always sendtrue
to get an instance of my object.I personaly created an helper to use in unit tests
and I use it this way :
Hope it helps
Thanks @stevejgordon for your time, I really appreciate it, and your feedback on previous experiences is precious. I’ll read the supplied articles carefully.
Well just to conclude:
If I still feel confuse with that I’ll contact your consulting service, for sure.
Thank you!