vrcmarcos/elasticmock

Elasticmock does not mock the Elasticsearch client

Opened this issue · 1 comments

wpm commented

I have written the following unit test.

from unittest import TestCase

from elasticmock import elasticmock
from elasticsearch import Elasticsearch


def some_function_that_uses_elasticsearch():
    client = Elasticsearch(hosts=[{"host": "localhost", "port": 9200}])
    id = client.index("test-index", {"a": "b"})
    print(id)
    return True


class TestClass(TestCase):
    @elasticmock
    def test_should_return_something_from_elasticsearch(self):
        self.assertIsNotNone(some_function_that_uses_elasticsearch())

If I run the test without a local instance of Elasticsearch running I get the following error.

elasticsearch.exceptions.ConnectionError: ConnectionError(<urllib3.connection.HTTPConnection object at 0x7ff720769370>: 
Failed to establish a new connection: [Errno 61] Connection refused) caused by: 
NewConnectionError(<urllib3.connection.HTTPConnection object at 0x7ff720769370>:
Failed to establish a new connection: 
[Errno 61] Connection refused)

If I step through in the debugger I see that the client is an ElasticSearch object and not a FakeElasticSearch object. Apparently the @elasticmock decorator has no effect.

I have the same problem with the pytest framework.

from elasticmock import elasticmock
from elasticsearch import Elasticsearch


@elasticmock
def test_mocked_elasticsearch_client():
    client = Elasticsearch(hosts=[{"host": "localhost", "port": 9200}])
    id = client.index("test-index", {"a": "b"})
    print(id)

The "Code Example" with FooService on the project homepage does work. But I can't figure out what is wrong with the variants I have here.

This is elasticmock 1.8.1 and elasticsearch 7.13.3 on OS X with Python 3.9.15.

I believe this is due to how @elasticmock is patching ES and how you're importing the client class.
@elasticmock uses unittest.mock.patch to essentially do setattr(elasticsearch, 'Elasticsearch', <mock_class>), so that any future getattr(elasticsearch, 'Elasticsearch') will return the mocked class. This happens at test function call time.
Before this happens, you already imported the (original) ElasticSearch class from the (unpatched) elasticsearch module into your namespace, and setattr does not work in-place so your namespace retains the original class reference, which your test function then uses.

Workarounds will involve ensuring you do your getattr on the elasticsearch module after elasticmock has done setattr a.k.a. inside your test function:
a) Move the elasticsearch import inside the test function.
b) Change your top-level import to be import elasticsearch, then access the Elasticsearch class inside the test function with elasticsearch.Elasticsearch (this is what I did for my use-case).

I don't know what a good prevention measure would be for this. I don't think elasticmock can see the Elasticsearch class reference in your local namespace, so it can't patch it for you.