Clean up test fixtures - amazon-web-services

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

Related

Test runners inconsistent with HttpClient and Mocking HttpMessageRequest XUnit

So let me start by saying I've seen all the threads over the wars between creating a wrapper vs mocking the HttpMethodRequest. In the past, I've done the wrapper method with great success, but I thought I'd go down the path of Mocking the HttpMessageRequest.
For starters here is an example of the debate: Mocking HttpClient in unit tests. I want to add that's not what this is about.
What I've found is that I have tests upon tests that inject an HttpClient. I've been doing a lot of serverless aws lambdas, and the basic flow is like so:
//some pseudo code
public class Functions
{
public Functions(HttpClient client)
{
_httpClient = client;
}
public async Task<APIGatewayResponse> GetData(ApiGatewayRequest request, ILambdaContext context)
{
var result = await _client.Get("http://example.com");
return new APIGatewayResponse
{
StatusCode = result.StatusCode,
Body = await result.Content.ReadStringAsAsync()
};
}
}
...
[Fact]
public void ShouldDoCall()
{
var requestUri = new Uri("http://example.com");
var mockResponse = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(expectedResponse) };
var mockHandler = new Mock<HttpClientHandler>();
mockHandler
.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
It.IsAny<HttpRequestMessage>(),
It.IsAny<CancellationToken>())
.ReturnsAsync(mockResponse);
var f = new Functions(new HttpClient(handler.Object);
var result = f.GetData().Result;
handlerMock.Protected().Verify(
"SendAsync",
Times.Exactly(1), // we expected a single external request
ItExpr.Is<HttpRequestMessage>(req =>
req.Method == HttpMethod.Get &&
req.RequestUri == expectedUri // to this uri
),
ItExpr.IsAny<CancellationToken>()
);
Assert.Equal(200, result.StatusCode);
}
So here's where I have the problem!
When all my tests run in NCrunch they pass, and pass fast!
When I run them all manually with Resharper 2018, they fail.
Equally, when they get run within the CI/CD platform, which is a docker container with the net core 2.1 SDK on a Linux distro, they too fail.
These tests should not be run in parallel (read the tests default this way). I have about 30 tests around these methods combined, and each one randomly fails on the moq verify portion. Sometimes they pass, sometimes they fail. If I break down the tests per test class and on run the groups that way, instead of all in one, then these will all pass in chunks. I'll also add that I have even gone through trying to isolate the variables per test method to make sure there is no overlap.
So, I'm really lost with trying to handle this through here and make sure this is testable.
Are there different ways to approach the HttpClient where it can consistently pass?
After lots of back n forth. I found two of situations from this.
I couldn't get parallel processing disabled within the docker setup, which is where I thought the issue was (I even made it do thread sleep between tests to slow it down (It felt really icky to me)
I found that all the tests l locally ran through the test runners were telling me they passed when about 1/2 failed on the docker test runner. What ended up being the issue was a magic string area when seeing and getting environment variables.
Small caveat to call out, Amazon updated their .NET Core lambda tools to install via dotnet cli, so this was updated in our docker image.

Lamda function reuse best practices

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.

How to refactor and test api service with Bookshelf models and Express server?

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.

Redux Unit Test - reducers and action creators

I'm recently learning Redux and writing Unit Test as part of TDD process using Jest
Im writing test for action creators and reducers. But i'm struggling with: can I make use of action creators in the reducers test?
import * as types from './../../constants/auth';
import * as actions from './../../actions/auth';
import reducer, {initialState} from './../auth';
can I do this
it('should set isFetching to true', () => {
const expectedState = {
...initialState,
isFetching: true
}
expect(
reducer(initialState, actions.loginPending())
).toEqual(expectedState)
});
instead of this?
it('should set isFetching to true', () => {
const expectedState = {
...initialState,
isFetching: true
}
expect(
reducer(initialState, {type: types.LOGIN_PENDING})
).toEqual(expectedState)
});
I came to this doubt because the official documentation use hard coded action in the reducers test:
expect(
reducer([], {
type: types.ADD_TODO,
text: 'Run the tests'
})
).toEqual([{
text: 'Run the tests',
completed: false,
id: 0
}])
I guess using hard coded actions is the best practice isn't?
Interesting question and I would say it depends how you run your test suite. Personally, I hardcode the actions because if you think about it, they declaratively explain what the reducer is expecting. The argument in favor of importing the actions is that if you ever change their source, the tests will not need to be updated. However, this also means you're expecting your actions to always be correct BEFORE running these tests.
If that's the case (if you always run your actions test suite before this one) then it would be reasonable to import them in your reducer test suite. The only argument against this logic would be that it's not as easy to have a new developer learn how your reducer works by only looking at the reducer test suite as they would also need to look at the actions source file to see what type of actions are dispatched.
On the other hand, hard-coding your actions is more declarative but does require you to update each reducer test if your action changes. The reason I still recommend this approach is that this is that it allows you to send more controlled data but I do agree that it increases maintenance costs.

Testing Reactive Extensions - How do I use the test scheduler with ToTask()?

I'm having trouble testing reactive code that's consuming a Task based service. In my class under test I consume the task and use ToObservable to do reactive-y things with it.
public void Method()
{
_svc.MyTaskServiceMethod().ToObservable().Select(....) //pipe it elsewhere and do interesting things.
}
Now in a unit test I'm testing some timing (using Moq for the service)
svcMock.Setup(x => x.MyTaskServiceMethod()).Returns(() =>
Observable.Return("VALUE", testScheduler)
.Delay(TimeSpan.FromMilliseconds(100), testScheduler)
.ToTask()
);
The problem is that despite using the test scheduler in the Return/Delay calls, the task itself is still completing on a separate thread. I'm seeing this by adding a couple of Console writes of the current managed thread id to the code.
svcMock.Setup(x => x.MyServiceMethod()).Returns(() =>
{
var task = Observable.Return("VALUE", testScheduler)
.Delay(TimeSpan.FromMilliseconds(1000), testScheduler)
.Do(x => { Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString() + " Obs"); })
.ToTask();
task.ContinueWith((_) =>
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString() + " Task");
});
return task;
});
The Do(..) executes on the primary testing thread, and happens exactly when I expect after a testSchduler.AdvanceBy(..) call.
The task continuation is still happening in a separate thread and basically doesn't execute until after the body of the unit test has finished. So in the body of my target, nothing ever really gets pushed through my task.ToObservable() observable.
Task continuations will use a task pool thread by default, so your continuation escapes the control of the test scheduler. If you specify the option TaskContinuationOptions.ExecuteSynchronously, it will use the same thread and the result will be posted to the observable as desired:
task.ContinueWith((_) =>
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString() + " Task");
}, TaskContinuationOptions.ExecuteSynchronously);
Addendum
You may find this related discussion on the Rx site quite illuminating on the subject of concurrency in TPL -> Rx transitions, and ToObservable() in particular.
Some time ago I co-authored a unit test library based on NUnit to help with precisely with Rx and TPL testing. For that we built a Test TPL scheduler to force all TPL tasks to run without concurrency. You can see the relevant code here: https://github.com/Testeroids/Testeroids/blob/master/solution/src/app/Testeroids/TplTestPlatformHelper.cs#L87