How Do I Use The CDK Template For Testing Whether A Step Function Has Tasks With Specific Properties? - testing

Use Case: I am current testing my CDK Step Function stacks. Whilst I've managed to test whether there is a state machine:
it("AWS::StepFunctions::StateMachine", () => template.resourceCountIs("AWS::StepFunctions::StateMachine", 1));
I would also like to test whether the state machine has a specific task, and the task has specific properties.
this.intoFifoQueueTask = new SqsSendMessage(this, "Send to Queue", {
queue: sqsStack.queueName,
timeout: Duration.minutes(15),
messageBody: TaskInput.fromObject({...}),
messageGroupId: JsonPath.stringAt("$.messageGroupId"),
integrationPattern: IntegrationPattern.WAIT_FOR_TASK_TOKEN,
resultPath: JsonPath.stringAt("$.result"),
});
How do I ensure this SqsSendMessage task is in the Step Function? How can I check whether it is a part of the definition? Is testing this granually necessary?

The CDK synthesizes State Machines definitions into the DefinitionString property, provided you are building with the CDK's state constructs.
First, capture the State Machine definition:
const definition = new Capture();
template.hasResourceProperties("AWS::StepFunctions::StateMachine", {
DefinitionString: definition,
});
The captured template value is typically an Fn::Join instrinsic function object type, not a string:
{ "Fn::Join": ["", ["{\"StartAt\"...."]] }
The SQS optimised integration has a synthesized resource type like states:::sqs:sendMessage. Assertions are easier if you match against the stringified definition:
expect(JSON.stringify(definition.asObject())).toMatch(/states:::sqs:sendMessage/);

Related

StimulusJS - how to ensure controller has all the necessary targets?

I'm trying to find a way to make my Stimulus controller more robust and maintainable by checking that all the required targets are present. If something is missing, I would like it to fail fast and loud.
Below is what I'm using so far:
export default class extends Controller {
static targets = ['name'];
connect() {
if (!that.hasNameTarget) {
throw new Error('expected to find name target');
}
}
}
Perhaps someone knows of a more idiomatic/clean solution?
Option 1 - use the Stimulus debugger tooling
Stimulus has a debug mode that logs out info/warnings etc for Stimulus controllers. You can enable this by stimulus.debug = true;
You can call this in your own controllers via this.application.logDebugActivity() - see https://github.com/hotwired/stimulus/blob/main/src/core/application.ts#L95
export default class extends Controller {
static targets = ['name'];
connect() {
if (!that.hasNameTarget) {
this.logDebugActivity(this.identifier, 'error', { message: 'target missing'});
throw new Error('expected to find name target');
}
}
}
Option 2 - Use the window.onerror callback
If you keep your current code where an error is thrown, Stimulus will not 'break' anything where possible as all calls within Stimulus use try/catch.
However, you can ensure that your error does something 'loud' by creating a onerror function.
See docs - https://stimulus.hotwired.dev/handbook/installing#error-handling
See an example where this can be used for something like Sentry https://blog.sentry.io/2016/01/04/client-javascript-reporting-window-onerror
You could also just be really loud and block the UI with something similar to this.
window.onerror = (message, url, lineNo, columnNo, error) => {
document.body.style.backgroundColor = 'red';
window.alert(message);
}
Reminders
Remember to only enable these debugging features in local development, you can do this with something like Webpack environment variables but this will be different depending on your tooling.
In production though you may want to push your onerror calls to whatever logging infrastructure you have.
stimulus.debug mode is quite 'noisy' and may be too much information, depending on your set up.

Vuex mutations and actions responsibilities

Except that a mutation must be synchronous whereas an action can contain asynchronous code, I find it difficult to establish some guidelines on how to correctly choose where the logic is supposed to be implemented.
The Vuex doc describes the mutations as transactions, which makes me think that each mutation must ensure as much as possible state coherence, potentially doing multiple changes in the store within the same mutation.
Let’s take for example the following state, storing documents that can either be loading or loaded:
const state = {
loading: []
loaded: []
}
const mutations = {
setLoaded(state, doc) {
state.loaded.push(doc)
// Remove doc from loading:
const index = state.loading.find(loadingDoc => doc.id === loadingDoc.id)
if (index !== -1) {
state.loading.splice(index, 1)
}
}
}
Here the mutation ensures that the doc is either in loading or loaded state, not both. The problem doing so is that the mutations could eventually end up having a lot of logic, which could make them more difficult to debug through the Vuex dev tools.
Some prefer to keep the mutations as simple as possible (eg. only one state change per mutation), but then it’s up to the actions (or the components in case they directly call mutations) to ensure state coherence, which could be more error-prone since, in the above example, you would need to remove the doc from loading state each time you call the “setLoaded” mutation.
As for data validation, unluckily we have no simple way to do it in mutations since mutations cannot return anything. So an action calling a mutation has no way to know that the data is invalid. Eg.:
const mutations = {
setValue(state, intValue) {
if (intValue < 0) {
// error
return
}
state.value = intValue;
}
}
Thus, it seems that data validation needs to be done in actions, with mutations assuming that the data they receive are correct.
What are your thoughts about this, and more generally about actions and mutations? Do you have guidelines on how to properly use them from your own experience?
Data validation MUST be done in actions if it encompasses multiple mutations and/or needs to signal to the caller if the operation failed.
My rule (and this will be argued...) is to always use async CALLS (with async/await) whenever possible, even if I am calling sync methods. Then my code is future proof if the sync method becomes async. And in the case you mentioned, it also allows a return value and/or error from Vuex.
let response
response = await anAsyncMethod()
response = await aSyncMethod()
response = await aVuexMutateOp()
response = await aVueActionOp()
Yes this adds a bit of processing overhead, but it is also just as clear by using async/await syntax. It streamlines the error processing as well, routing it to a catch block.
Because mutations have the restriction that they MUST be synchronous AND CANNOT call other mutations AND DO NOT return a value demonstrates that the mutate should only be used in the simplest of situations.

In TestCafe is there a way to know if the test passed or failed in after hook?

I am trying to mark tests as pass/failed through a rest API (Zephyr) while my testcafe tests are running. I was wondering if it's possible in the after or afterEach hook to know if the test passed/failed so that I can run some script based on the result.
Something like:
test(...)
.after(async t => {
if(testFailed === true) { callApi('my test failed'); }
})
I see two ways in which to solve your task. First, do not subscribe to the after hook, but create your own reporter or modify the existing reporter. Please refer to the following article: https://devexpress.github.io/testcafe/documentation/extending-testcafe/reporter-plugin/#implementing-the-reporter
 
The most interesting method there is reportTestDone because it accepts errs as a parameter, so you can add your custom logic there.
The second approach is using sharing variables between hooks and test code
You can write your test in the following manner:
test(`test`, async t => {
await t.click('#non-existing-element');
t.ctx.passed = true;
}).after(async t => {
if (t.ctx.passed)
throw new Error('not passed');
});
Here I am using the shared passed variable between the test code and hook. If the test fails, the variable will not be set to true, and I'll get an error in the after hook.
This can be determined from the test controller, which has more information nested within it that is only visible at run time. An array containing all the errors thrown in the test is available as follows
t.testRun.errs
If the array is populated, then the test has failed.

Mocha and jsdom - How to set a window variable

I know you're not supposed to do this, but I'm trying to write some tests with legacy code still using requirejs that have a few window variables floating around.
Basically I'm trying to write a mocha test and include some predefined global variables that a different file would use later. I'm trying to do the following, but it seems the global variable "container" isn't populated when accessing it later.
global.document = require('jsdom').jsdom('<html></html>');
global.window = document.defaultView;
global.$ = require('jquery')(window);
// this should be available everywhere as far as I can tell...
global.container= {};
global.window.container= global.container;
// legacy scripts still using requirejs, so we need to load the require config here
var requirejs = require('testing-setup').requirejs;
// chai is nice
require('chai').should();
describe('model tests', function () {
var model;
// before we begin the tests, we need to require in all the necessary modules
before(function (done) {
window.container= {
dateFormat: false
};
requirejs(['Model', 'common', 'date'], function (Model) {
// load some dummy data out of a file
model= new Model(require('./test-data.js').item);
done();
});
});
// run some more tests down here, I'll spare you those
});
The script being loaded called "common" above has a reference to the global "container" object that lives on the window. Apparently what I have is not correct. Is there no way to set up a shared global variable in jsdom? I know it's not the standard way of doing things, so please spare the lectures. Refactoring all that legacy code right now is not really a feasible option.
Ah, it turns out this is the correct way of doing it. It appears the jsdom/nodejs differentiate the difference between window and global. If you want something to be available everywhere in every file in that session, it needs to be on the global namespace. The window is explicitly window.

Flux architecture circular dependency

I have started learning Facebook's Flux architecture. I am trying to make a simple login screen. I have followed the flux-chat sample app to create the screen. I have a problem of circular dependency between ServerActionCreator and WebAPIUtils. Please see the code below.
ServerActionCreator.js
var AppDispatcher = require('../dispatcher/AppDispatcher');
var Constants = require('../constants/Constants');
var WebAPIUtils = require('../utils/WebAPIUtils');
var ActionTypes = Constants.ActionTypes;
module.exports = {
receiveLoginStatus: function(status){
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVE_LOGIN_STATUS,
status: status
});
},
loginSubmit: function(data){
WebAPIUtils.login(data);
}
}
WebAPIUtils.js
var ServerActionCreator = require('../actions/ServerActionCreator');
module.exports = {
login: function (data) {
//Mock server API call
var status = JSON.parse('{"status":"success"}');
ServerActionCreator.receiveLoginStatus(status);
}
};
As you can see ServerActionCreator depends on WebAPIUtils and WebAPIUtils depends ServerActionCreator.
I think, due to circular dependency WebAPIUtils becomes an empty object and I am getting "undefined is not a function" error when loginSubmit function in ServerActionCreator is called. Screenshot below.
How to handle this scenario? or Is there any alternative way? Any help is much appreciated.
Whenever you have a circular dependency between modules, a common solution is to either combine the modules or to create a third entity that will break the cycle.
In your case, I'd argue that you could move loginSubmit to a different action creators module. It's actually a user action, not a sever action, anyway. So maybe loginSubmit could go in UserActionCreators.js along with any number of other user action creator methods.
Another solution to your problem (and to circular dependencies in general) is to make your methods more pure, removing dependencies and instead passing in dependencies as arguments. So WebAPIUtils.login() could take a second argument, which would be the success callback. Thus:
WebAPIUtils.login(data, ServerActionCreator.receiveLoginStatus)