question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Unit tests : Make all concrete class mockable

See original GitHub issue

Hi,

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:closed
  • Created 2 years ago
  • Comments:6 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
NicolasREY69330commented, Nov 9, 2021

@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") returned true or false, but as it is called from my ctor, I needed to always send true to get an instance of my object.

I personaly created an helper to use in unit tests

    public static class Helper
    {
        public static IElasticClient InMemoryElasticSearchClientFactory()
        {
            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:9999"));
            var settings = new ConnectionSettings(connectionPool, connection)
                .DefaultIndex("project")
                .DisableDirectStreaming();
            var client = new ElasticClient(settings);
            return client;
        }

and I use it this way :

        [Fact]
        public async Task When_NoGeocodedCityFound_Expect_GetDensityReturnsRural()
        {
            // Arrange
            var repositoryMock = new Mock<GeocodedCityElkRepository>(InMemoryElasticSearchClientFactory(), 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 density = await repositoryMock.Object.GetDensity("11111", "city", "FR");

            // Assert
            Assert.Equal(Domain.Enums.DensityCode.Rural, density);
        }

Hope it helps

1reaction
NicolasREY69330commented, Jun 10, 2021

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:

  • I will start using Elastic as main DB for non critical data, that won’t change a lot (ie fallback for undistributed payload for future retry as the schema is really flexible) and with reasonable volume (3000 to 100 000 records a day) or for data that expires in time (based on any business rules)
  • For critical data (ie Orders, Payments …) I plan to use an event store DB as primary DB and project events into Elastic models to query them with high flexibility

If I still feel confuse with that I’ll contact your consulting service, for sure.

Thank you!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Is it recommended to mock concrete class?
In theory there is absolutely no problem mocking a concrete class; we are testing against a logical interface (rather than a keyword ...
Read more >
TDD: testing an abstract class. Test all concrete classes or ...
My question is: What you think about making test for this abstract class? Would you write tests only for its subclasses or would...
Read more >
When to use mocks insntead of concrete class
In short: You mock objects that are required for your class UnderTest during unit-testing. Obviously you don't care about how they work, that's...
Read more >
Unit Testing in C# With Moq - Wake up the Testing Genius ...
Mocking is a popular technique for unit testing that creates test double objects, which gives you the ability to control the behavior of...
Read more >
Elasticsearch C# Nest unit tests with Moq : fail to mock ...
So for the mocking I tried to set the property anyway on the concrete class, but it failed with all the following methods,...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found