0

I need to write unit tests for the following class function, which returns the EC2 instances of AWS, to be tested.

class C:
    def f(cls, session, filters):
            # ....
            instances = session.resource('ec2').instances.filter(Filters=filters)
            # ....
            return instances

I want to test if session.resource('ec2').instances.filter() is called with parameter Filters=filters.

import boto3
import boto3.session
import pytest
from moto import mock_ec2

@mock_ec2
def test_get_nodes():
    '''Test get node'''
    session = boto3.session.Session(region_name='')
    filters = None
    C.f(session, filters)
    assert session.resource('ec2').instances.filter.call_count == 1

However, the test got the following error?

>           raise ValueError("Invalid endpoint: %s" % endpoint_url)
E           ValueError: Invalid endpoint: https://ec2..amazonaws.com

I don't want the test function connect to the AWS console actually. Anyway, it got the following error after I set the region_name with a proper value:

>       assert session.resource('ec2').instances.filter.call_count == 1
E       AttributeError: 'function' object has no attribute 'call_count'
ca9163d9
  • 27,283
  • 64
  • 210
  • 413

2 Answers2

1

It looks like you're trying to use traditional mocking approaches (like testing the number of calls, catching the arguments, etc) in combination with Moto. But Moto does not expose any of that information.

Moto is more like an in-memory AWS instance. That means you can use any (supported) boto3 request, and Moto will intercept that request and behave like AWS does - but without actually making the request (and incurring any costs).

A traditional Moto test case (in pseudo-case) would look like this:

assert len(session.resource('ec2').instances) == 0
ec2_client.run_instances(..)
assert len(session.resource('ec2').instances) == 2
assert len(session.resource('ec2').instances.filter) == 1

None of that checks whether methods are called - it uses boto3-requests to verify that previous boto3-requests were successful, like you're writing an integration test against AWS itself.

Edit:
If you are concerned about making requests to AWS itself, it's worth setting dummy environment variables that reset your access key/secret key during testing.
See http://docs.getmoto.org/en/latest/docs/getting_started.html#recommended-usage

It is also possible to use a nonexistent-region, for even more peace of mind.

os.environ["MOTO_ALLOW_NONEXISTENT_REGION"] = True
os.environ["AWS_DEFAULT_REGION"] = "antarctica"

See the FAQ here: http://docs.getmoto.org/en/latest/docs/faq.html#can-i-mock-the-default-aws-region

Bert Blommers
  • 1,788
  • 2
  • 13
  • 19
  • The function `C.f()` in my question just queried the AWS. So `assert len(session.resource('ec2').instances) == 0` will not tell if the function called the query function? – ca9163d9 Jan 26 '22 at 05:09
  • Correct - that is not within the scope of Moto – Bert Blommers Jan 26 '22 at 10:19
  • OK. Thanks. I may have to use traditional monkey patch for the test. – ca9163d9 Jan 26 '22 at 15:01
  • `os.environ["MOTO_ALLOW_NONEXISTENT_REGION"] = True` raised error. I changed it to `os.environ["MOTO_ALLOW_NONEXISTENT_REGION"] = "True"`. – ca9163d9 Jan 28 '22 at 13:43
0

The region is not set here :

session = boto3.session.Session(region_name='')

Try with the region filled in.

Devang Sanghani
  • 731
  • 5
  • 14
  • The session will be mocked so the region shouldn't matter. I don't want the test function connect to the AWS console actually. – ca9163d9 Jan 25 '22 at 06:12
  • I've updated the question with the `region_name` set. – ca9163d9 Jan 25 '22 at 06:17
  • What exactly are you trying to assert here? There is no 'call_count' attribute for the filter. https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html – Devang Sanghani Jan 25 '22 at 06:38
  • I want to mock the session object; so in the unit test, I want to make sure that the execution of `C.f(session, filters)` calls `session.resource('ec2').instances.filter()` once. – ca9163d9 Jan 25 '22 at 06:47
  • Then probably this should suffice : ```instance = C.f(session, filters) assert instance is not None``` If you print `instance`, you will get this : ```ec2.instancesCollection(ec2.ServiceResource(), ec2.Instance)``` – Devang Sanghani Jan 25 '22 at 07:07
  • If you found this working okay, you can mark it as an accepted answer. – Devang Sanghani Jan 25 '22 at 07:29
  • It cannot be asserting instance. There shouldn't be any actual boto3 method calls. The unit tests just need to verify the function "tried" to call `.filter()`. – ca9163d9 Jan 25 '22 at 07:56