How to mock a synchronous lambda invocation for testing - python

I have a lambda function which in turn calls another lambda function synchronously. I am trying to write tests for the parent function using pytest but I am not sure what is the best way to mock the child lambda function using moto and how does all of it come together in a test function. Can anyone please guide me here.
Parent function
app.py
import boto3
def launch_child_lambda(func_name, data):
lambda_client = boto3.client('lambda')
result = lambda_client.invoke(
FunctionName=func_name,
Payload=json.dumps(data)
)
return result
.....
#omitting code for brevity
.....
def handler(event,context):
//some logic to be tested
response=launch_child_lambda(func_name,data)
//some other logic to be tested

Related

Unit testing lambdas which call dynamodb in python

I am having lambdas which use boto3.client() to connect to a dynamoDB.
I tried to test it like this
#mock.patch("boto3.client")
def test(self, mock_client, test):
handler(event, context)
print(mock_client.call_count) # 1
print(mock_client.put_item.call_count) # 0
However, the mock_client.call_count is 1, but not the put_item_call_count.
My handler looks like this:
def handler(event, context):
dynamodb = boto3.client('dynamodb')
response = dynamodb.put_item(// same attributed)
Any suggestion, how to test if the correct item gots putted in the database without using moto?
I believe you're very close, there's just one tiny problem.
When your mocked boto3.client is called, it returns another mock and you want to evaluate that mocks call_count. By accessing the return_value of the original mock, you get access to the created magic mock.
#mock.patch("boto3.client")
def test(self, mock_client, test):
handler(event, context)
print(mock_client.call_count)
# .return_value refers to the magic mock that's
# created when boto3.client is called
print(mock_client.return_value.put_item.call_count)
What you're currently evaluating is the call count of boto3.client.put_item and not boto3.client("dynamodb").put_item().

Testing two methods in an Azure function in python

I am writing tests for my azure function, and for some reason - I can't mock a function call. I should also mention this is the first time I'm writing a python test case so be nice :)
def main(req: func.HttpRequest) -> func.HttpResponse:
logging.info('Python HTTP trigger function processed a request.')
try:
req_body = req.get_json()
except ValueError as error:
logging.info(error)
download_excel(req_body)
return func.HttpResponse(
"This HTTP triggered function executed successfully.",
status_code=200
)
so thats the initial function. This function calls download_excel and pass the request body. The next function receives the request body, and writes that excel to a blob storage.
def download_excel(request_body: Any):
excel_file = request_body["items_excel"]
#initiate the blob storage client
blob_service_client = BlobServiceClient.from_connection_string(os.environ["AzureWebJobsStorage"])
container = blob_service_client.get_container_client(CONTAINER_NAME)
blob_path = "excel-path/items.xlsx"
blob_client = container.get_blob_client(blob_path)
blob_client.upload_blob_from_url(excel_file)
Those are the two functions. receive a file, save it to blob storage, but i can't mock the download_excel call in the main function. I've tried using mock, patch, went through all sorts of links, and i just can't find a way to achieve this. Any help would be appreciated. here is what i currently have in the test file.
class TestFunction(unittest.TestCase):
##patch('download_excel')
def get_excel_files_main(self):
"""Test main function"""
req = Mock()
resp = main(req)
# download_excel= MagicMock()
self.assertEqual(resp.status_code, 200)
commenting out the function call in the function and in the test makes the test pass, but i need to know how to mock the download_excel call. I'm still going to write a test case for the download_excel function, but will cross that bridge when i get to it.
Figured it out. I'm pretty silly. Main issue was in an azure function, I figured since there was no class I can ignore every example in the doc that had to do with classes.
The trick is to use the function name as a class. so say you have function name - http_trigger, and a init.py file within that function folder. Within that init file - you have your main method, and a second method thats called from the main method - you can use MagicMock.
import function_name
def test_main_function(self):
"""Testing main function"""
function_name.second_method_being_called = MagicMock()
Thats it. Thats how you mock it! *facepalm

Django Mock an imported function used in a class function as part of a unit test

So I'm writing tests for my django application and I have successfully mocked quite a few external api calls that aren't needed for tests however one is tripping me up which is send_sms. To start here is the code:
a/models.py:
from utils.sms import send_sms
...
class TPManager(models.Manager):
def notification_for_job(self, job):
...
send_sms()
...
class TP(models.Model):
objects = TPManager()
...
p/test_models.py:
#patch('a.models.send_sms')
#patch('p.signals.send_mail')
def test_tradepro_review_job_deleted(self, send_mail, send_sms):
job = Job.objects.create(
tradeuser=self.tradeuser,
location=location,
category=category,
details="sample details for job"
)
The Job object creation triggers TP.objects.notification_for_job via its perform_create method here:
p/views.py:
def perform_create(self, serializer):
job = serializer.save(tradeuser=self.request.user.tradeuser)
if settings.DEV_MODE:
from a.models import TP
job.approved = True
job.save()
TP.objects.notification_for_job(job)
I have tried mocking a.models.TP.objects.notification_for_job, utils.sms.send_sms, a.models.TPManger.notification_for_job all to no avail. This is a pretty complex flow but I believe I have tried the main mock candidates here and was wondering if anybody knows how to either mock the notification_for_job function or send_sms function properly mostly just to prevent these api call that inevitably fail due to my test environment.
Any ideas are greatly appreciated!

How to deploy a script in AWS lambda

I have two scripts which I need to deploy in AWS lambda, I have never done it before, from the documentation I created kind of a few steps which would summarize the flow:
Create a lambda function
Install boto3
Use invoke function
Lets say I have a simple function:
def first_function():
return print('First function')
When I go to AWS -> Lambda -> Functions -> Create function I get to the configuration part where in the editor I see this:
import json
def lambda_handler(event, context):
# TODO implement
return {
'statusCode': 200,
'body': json.dumps('Hello from Lambda!')
}
Is this how I should edit this to deploy my function:
import json
def lambda_handler(event, context):
# TODO implement
return {
def first_function():
return print('First function')
first_function()
}
Tha lambda_handler that shows up when you create a function in the console is simply boiler plate code.
You can name your handler anything, or simply place your function code under lambda_handler
def lambda_handler(event, context):
return print('First function')
The name lambda_handler is configurable, meaning you could use the code
def first_function(event, context):
return print('First function')
But you'll need to ensure that the function is configured to use first_function as it's handler.
I'd recommend reading through the docs specific to python handlers
Whatever functionality you need to implement in your lambda, you should write within the lambda_handler. If you want to refer to other smaller function you can define it outside the lambda handler function and can refer to it in the handler. So it might be like below
import x
def functiona():
print(‘something’)
def functionb():
print(‘somethingelse’)
def lambda_handler(event,context)
print(‘lambda entry point)
functiona()
functionb()
Since the module will first be imported, you can still write code outside of functions although it is usually not a good practice since you cannot access the context and parameters you have sent to lambda.

How to patch a function that a Flask view calls

My webapp wants to send a message to AWS SQS with boto and I'd want to mock out sending the actual message and just checking that calling send_message is called. However I do not understand how to use python mock to patch a function that a function being tested calls.
How could I achieve mocking out boto con.send_message as in the pseudo-like code below?
views.py:
#app.route('/test')
def send_msg():
con = boto.sqs.connect_to_region("eu-west-1",aws_access_key_id="asd",aws_secret_access_key="asd")
que = con.get_queue('my_queue')
msg = json.dumps({'data':'asd'})
r=con.send_message(que, msg)
tests.py
class MyTestCase(unittest.TestCase):
def test_test(self):
with patch('views.con.send_message') as sqs_send:
self.test_client.get('/test')
assert(sqs_send.called)
To do this kind of test you need patch connect_to_region(). When this method is patched return a MagicMock() object that you can use to test all your function behavior.
Your test case can be something like this one:
class MyTestCase(unittest.TestCase):
#patch("boto.sqs.connect_to_region", autospec=True)
def test_test(self, mock_connect_to_region):
#grab the mocked connection returned by patched connect_to_region
mock_con = mock_connect_to_region.return_value
#call client
self.test_client.get('/test')
#test connect_to_region call
mock_connect_to_region.assert_called_with("eu-west-1",aws_access_key_id="asd",aws_secret_access_key="asd")
#test get_queue()
mock_con.get_queue.assert_called_with('my_queue')
#finaly test send_message
mock_con.send_message.assert_called_with(mock_con.get_queue.return_value, json.dumps({'data':'asd'}))
Just some notes:
I wrote it in a white box style and check all calls of your view: you can do it more loose and omit some checks; use self.assertTrue(mock_con.send_message.called) if you want just check the call or use mock.ANY as argument if you are not interested in some argument content.
autospec=True is not mandatory but very useful: take a look at autospeccing.
I apologize if code contains some error... I cannot test it now but I hope the idea is clear enough.

Categories