Should Lambda functions call other Lambda functions or should they be self contained?
My environment is
Serverless Framework
Nodejs
AWS API Gateway
AWS Lamda
AWS DynamoDB
I've build several CRUD for API resources each Dynamo table and now I'm creating some specialized ones that cross tables.
If I have a function createTeamForecast, and I need to get a single row from table Team, should I import the function getTeam or just write the Dynamo query. I lean to importing the function, but I haven't see anything saying that is OK.
getTeam.js
import * as dynamoDbLib from "./libs/dynamodb-lib";
import { apiResponse } from "./libs/response-lib";
export async function main(event, context, callback) {
const params = {
TableName: "teams",
Key: {
id: event.pathParameters.team_id
}
};
try {
const result = await dynamoDbLib.call("get", params);
if (result.Item) {
// Return the retrieved item
callback(null, apiResponse(200,"OK",result.Item));
} else {
callback(null, apiResponse(404, "Team not found."));
}
} catch (e) {
callback(null, apiResponse(500,'Server error',e));
}
}
In my createTeamForecast, can I just import that function and then call it.
import { main as getTeam } from "./getTeam";
My alternative is to just do a Dynamo get and check results within my createTeamForecast.js function. That's more self contained, but not very DRY.
The way that Serverless and Lambda manage the functions, it feels a little disconnected. Anyone have any pros or cons?
It's reasonable to import the code you need from another module rather than rewriting it. This has the side benefit of making it easier to maintain your application because you won't have duplicated logic all over the place.
The trick with serverless applications is finding the balance between code re-use and separation of concerns. The specifics of how to do this are somewhat application dependent. However, if you're putting too much code into each function then it's likely that your application is too tightly coupled and could use decomposition into smaller functions that more tightly model their problem space. If you find large swaths of shared code within your Lambda functions that might be a good indicator that they should be refactored into other functions.
If you're modeling really complex business domains then you may also want to consider calling other Lambda functions from within Lambda functions or investigating AWS Step Functions which provide a state machine on top of Lambda.
Related
I was planning on creating a new Layer for logging to gcp LogExplorer. Because we are working in rust, the api for logging is an async function call.
The tracing Layer trait exposes on_event function which is not async, which is where the problem comes in (link here)
I was thinking of ways to solve this, a simple solution which came to my mind was to send the events (the relevant fields) through a channel and then consume the channel on a tokio::spawn where my gcp client can make the grpc calls.
What I have in mind looks roughly like this (glossing over a lot of the details)
fn get_fields_from_event(event: &tracing::Event<'_>) -> LogEntry {
unimplemented!("some code here to convert to a type I can use with grpc coded for logging in gcp");
}
let (events_sender, events_receiver) = futures::channel::mpsc();
impl<S> Layer<S> for CustomLayer
where
S: tracing::Subscriber,
{
fn on_event(
&self,
event: &tracing::Event<'_>,
_ctx: tracing_subscriber::layer::Context<'_, S>,
) {
events_sender.clone().send(get_fields_from_event(event))
}
}
tokio::spawn(async move {
events_receiver.for_each(|event| async {grpc_client.send(event);});
});
Some obvious flaws I see with this approach is:
if the tokio::spawn silently exists, we might loose logging (I can safeguard against it .. but logging is kinda important for debugging so I would have to restart the process completely or manage the tokio::spawn process)
tokio::spawn itself feels a bit weird to get async based logging client supported. For some reason this feels a bit weird to me.
Are there any other alternatives or insights I am missing, or some crate which might provide support for working with async logging clients and integrating them in tracing Layer.
Thanks!
I have a CloudFormation stack that defines a GraphQL API powered by DynamoDB. I would like to run a test script that:
Creates a standard set of fixtures.
Runs various tests, including creating, modifying, and deleting data.
Deletes all of the fixtures and any other objects created during the test.
The “clean way” to do this would be to create a new stage for the tests, but this is extremely time-consuming (in terms of wall-clock time spent waiting for the result).
The “hard way” would be to keep precise track of every DynamoDB record created during the testing process and then delete them afterward one by one (and/or using many batch updates). This would be a huge pain to code, and the likelihood of error is very high.
An intermediate approach would be to use a dedicated pre-existing stage for integration tests, wipe it clean at the end of the tests, and make sure that only one set of tests is running at a time. This would require writing a script to manually clear out the tables, which sounds moderately less tedious and error-prone than the “hard way”.
Is there an established best practice for this? Are there other approaches I haven't considered?
How long does it take to deploy the stack?
If it is only a small portion of the time that takes to run the tests use the "clean way", otherwise use the intermediate approach of having a dedicated test stack already deployed.
You don't have to write scripts.
I actually wrote a testing library for this purpose exactly:
https://github.com/erezrokah/aws-testing-library/blob/master/src/jest/README.md#tohaveitem
Usage example (TypeScript):
import { clearAllItems } from 'aws-testing-library/lib/utils/dynamoDb';
import { invoke } from 'aws-testing-library/lib/utils/lambda';
describe('db service e2e tests', () => {
const region = 'us-east-1';
const table = 'db-service-dev';
beforeEach(async () => {
await clearAllItems(region, table);
});
afterEach(async () => {
await clearAllItems(region, table);
});
test('should create db entry on lambda invoke', async () => {
const result = await invoke(region, 'db-service-dev-create', {
body: JSON.stringify({ text: 'from e2e test' }),
});
const lambdaItem = JSON.parse(result.body);
expect.assertions(1);
await expect({ region, table, timeout: 0 }).toHaveItem(
{ id: lambdaItem.id },
lambdaItem,
);
});
});
If you do write the scripts yourself you might need to consider eventual consistency and retry (as the data might not be available directly after write)
I'd simply delete this "test stack" in the end of the test and let CloudFormation clean up DynamoDB for you -- check DeletionPolicy documentation.
You might want to trigger/hook stack deletion from your CI environment, whatever it is. As an example, I found this CodePipeline walkthrough: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/continuous-delivery-codepipeline-basic-walkthrough.html
I'm writing test for firebase database onWrite trigger function in offline mode using stubbing as suggested in the docs and the github sample.
But I'm confused how to stub multiple .child().val() for value in my data coming in the trigger. Here is my progress to create data object:
let data={
before: {
val : ()=> null,
},
after:{
val: ()=> {/*after data here */},
child: {
"child1": "How have value here from val",
"child2": "How have value here from val"
}
}
}
Since my function is a bit complex so there are lots of value reads from the incoming data (2 to 3 level nesting) and then there are multiple access to database as well.
So is there any easy way to stub all these calls ? Or is the stubbing is only good for functions which have simple data and less read access to database?
P.S: I used online mode testing as well but that changes the state of the database which I cannot restore then
Testing functions with firestore is not easy. I have used a lot of energy to try different patterns, and ended up with this strategy.
Create generic methods in your project, for extracting data. For example a function called collectionQuery, docQuery etc.
Initialise the functions test in online mode. (this way you tests will not endless complain about things not being stubbed and mocked :))
Now in your tests, you can simply spy on your functions from step 1. If you create the functions, so that they return values, it becomes really easy.
Example of collectionQuery function (typescript):
/**
* The purpose of this relatively simple function is to query firestore!
* By wrapping the operation in a helper function, it becomes much easier to test.
* #export
* #template T Provide the variable type for the function
* #param {FirebaseFirestore.Firestore} afs The firestore instance, usefull for switching between functions/admin
* #param {FirebaseFirestore.Query} query The query to perform. By accepting a query, it becomes very flexpible
* #returns {Promise<Array<T>>} The result as an array
*/
export async function collectionQuery<T>(afs: FirebaseFirestore.Firestore, query: FirebaseFirestore.Query): Promise<Array<T>> {
try {
if (afs == null || query == null) {
throw new Error(`You must provide an firestore instance, or a query`);
}
const snapshot = await query.get();
const returnData: T[] = snapshot.docs.map((doc) => doc.data() as T);
return returnData;
} catch (error) {
throw new Error(`collectionQuery failed unexpected with: ${error.message}`);
}
}
The benefit is that in a unit test, I can now simply spy on this function. I am using Jest, so something like this:
jest.spyOn(firehelpers, 'collectionQuery').mockReturnValue([]} // simulate no data returned by query
jest.spyOn(firehelpers, 'collectionQuery').mockReturnValue([{foo: 'bar'}]} // One result returned
This strategy makes it easy to test functions that read and write to the db/firestore. Could also be used in combination with stubbing on the firebase libraries if needed.
Let me know if it helps :)
I have the following services that return Bookshelf model data. The server is built on express. My core question is: I want to write tests for the services. Secondarily, I'm not sure if the services tests should include interaction with db; as you can see below, the services are currently intertwined with express.
// services.js
import Region from "../../models/region";
import Subregion from "../../models/subregion";
import bookshelf from "../../bookshelf.config";
/** Regions **/
export const getRegions = (req, res, next) => {
Region.forge()
.fetchAll()
.then(regions => {
log.info("Got all regions");
res.status(200).json(regions);
})
.catch(next);
};
/** Subegions **/
export const getSubregions = (req, res, next) => {
Subregion.forge()
.fetchAll({
columns: ["id", "name"],
})
.then(subregions => {
res.status(200).json(subregions);
})
.catch(next);
};
Questions
1. Whats is the proper way to test a function like getRegions?
2. Do best practices require getRegions and getSubregions to be extracted from the express context?
You have to analyze what your function does and what is the best way to test it. Looking at the getRegions function it just fetches all models of a certain type and returns the result as JSON to the user.
Given this, you have very little logic of your own, it's just a little bit of glue between two modules, so it doesn't make sense to unit test that function because you would just be testing if the used modules (Bookshelf and Express) are working properly, which should be beyond the scope of your project.
However, you probably do want to test if your API is responding correctly with various user inputs, so you should do some integration testing with something like SuperTest.
As for testing with an actual database or mocking it I think that's just a matter of personal opinion or project goals.
getRegions is just a function. You'd test it like a normal function. You would need to mock the Express' res, req, and next objects. In addition to the Express objects, you would need to mock Bookshelf/knex as well since you don't want to depend on an actual database. See this answer for bookshelf testing.
It is already extracted from Express' context since you defined it as a function. If you had defined it as app.post('/example' (req, res, next) => {}), then that would be coupled with Express.
First up, where my knowledge is at:
Unit Tests are those which test a small piece of code (single methods, mostly).
Integration Tests are those which test the interaction between multiple areas of code (which hopefully already have their own Unit Tests). Sometimes, parts of the code under test requires other code to act in a particular way. This is where Mocks & Stubs come in. So, we mock/stub out a part of the code to perform very specifically. This allows our Integration Test to run predictably without side effects.
All tests should be able to be run stand-alone without data sharing. If data sharing is necessary, this is a sign the system isn't decoupled enough.
Next up, the situation I am facing:
When interacting with an external API (specifically, a RESTful API that will modify live data with a POST request), I understand we can (should?) mock out the interaction with that API (more eloquently stated in this answer) for an Integration Test. I also understand we can Unit Test the individual components of interacting with that API (constructing the request, parsing the result, throwing errors, etc). What I don't get is how to actually go about this.
So, finally: My question(s).
How do I test my interaction with an external API that has side effects?
A perfect example is Google's Content API for shopping. To be able to perform the task at hand, it requires a decent amount of prep work, then performing the actual request, then analysing the return value. Some of this is without any 'sandbox' environment.
The code to do this generally has quite a few layers of abstraction, something like:
<?php
class Request
{
public function setUrl(..){ /* ... */ }
public function setData(..){ /* ... */ }
public function setHeaders(..){ /* ... */ }
public function execute(..){
// Do some CURL request or some-such
}
public function wasSuccessful(){
// some test to see if the CURL request was successful
}
}
class GoogleAPIRequest
{
private $request;
abstract protected function getUrl();
abstract protected function getData();
public function __construct() {
$this->request = new Request();
$this->request->setUrl($this->getUrl());
$this->request->setData($this->getData());
$this->request->setHeaders($this->getHeaders());
}
public function doRequest() {
$this->request->execute();
}
public function wasSuccessful() {
return ($this->request->wasSuccessful() && $this->parseResult());
}
private function parseResult() {
// return false when result can't be parsed
}
protected function getHeaders() {
// return some GoogleAPI specific headers
}
}
class CreateSubAccountRequest extends GoogleAPIRequest
{
private $dataObject;
public function __construct($dataObject) {
parent::__construct();
$this->dataObject = $dataObject;
}
protected function getUrl() {
return "http://...";
}
protected function getData() {
return $this->dataObject->getSomeValue();
}
}
class aTest
{
public function testTheRequest() {
$dataObject = getSomeDataObject(..);
$request = new CreateSubAccountRequest($dataObject);
$request->doRequest();
$this->assertTrue($request->wasSuccessful());
}
}
?>
Note: This is a PHP5 / PHPUnit example
Given that testTheRequest is the method called by the test suite, the example will execute a live request.
Now, this live request will (hopefully, provided everything went well) do a POST request that has the side effect of altering live data.
Is this acceptable? What alternatives do I have? I can't see a way to mock out the Request object for the test. And even if I did, it would mean setting up results / entry points for every possible code path that Google's API accepts (which in this case would have to be found by trial and error), but would allow me the use of fixtures.
A further extension is when certain requests rely on certain data being Live already. Using the Google Content API as an example again, to add a Data Feed to a Sub Account, the Sub Account must already exist.
One approach I can think of is the following steps;
In testCreateAccount
Create a sub-account
Assert the sub-account was created
Delete the sub-account
Have testCreateDataFeed depend on testCreateAccount not having any errors
In testCreateDataFeed, create a new account
Create the data feed
Assert the data feed was created
Delete the data feed
Delete the sub-account
This then raises the further question; how do I test the deletion of accounts / data feeds? testCreateDataFeed feels dirty to me - What if creating the data feed fails? The test fails, therefore the sub-account is never deleted... I can't test deletion without creation, so do I write another test (testDeleteAccount) that relies on testCreateAccount before creating then deleting an account of its own (since data shouldn't be shared between tests).
In Summary
How do I test interacting with an external API that effects live data?
How can I mock / stub objects in an Integration test when they're hidden behind layers of abstraction?
What do I do when a test fails and the live data is left in an inconsistent state?
How in code do I actually go about doing all this?
Related:
How can mocking external services improve unit tests?
Writing unit tests for a REST-ful API
This is more an additional answer to the one already given:
Looking through your code, the class GoogleAPIRequest has a hard-encoded dependency of class Request. This prevents you from testing it independently from the request class, so you can't mock the request.
You need to make the request injectable, so you can change it to a mock while testing. That done, no real API HTTP requests are send, the live data is not changed and you can test much quicker.
I've recently had to update a library because the api it connects to was updated.
My knowledge isn't enough to explain in detail, but i learnt a great deal from looking at the code. https://github.com/gridiron-guru/FantasyDataAPI
You can submit a request as you would normally to the api and then save that response as a json file, you can then use that as a mock.
Have a look at the tests in this library which connects to an api using Guzzle.
It mocks responses from the api, there's a good deal of information in the docs on how the testing works it might give you an idea of how to go about it.
but basically you do a manual call to the api along with any parameters you need, and save the response as a json file.
When you write your test for the api call, send along the same parameters and get it to load in the mock rather than using the live api, you can then test the data in the mock you created contains the expected values.
My Updated version of the api in question can be found here.
Updated Repo
One of the ways to test out external APIs is as you mentioned, by creating a mock and working against that with the behavior hard coded as you have understood it.
Sometimes people refer to this type of testing as "contract based" testing, where you can write tests against the API based on the behavior you have observed and coded against, and when those tests start failing, the "contract is broken". If they are simple REST based tests using dummy data you can also provide them to the external provider to run so they can discover where/when they might be changing the API enough that it should be a new version or produce a warning about not being backwards compatible.
Ref: https://www.thoughtworks.com/radar/techniques/consumer-driven-contract-testing