This is currently a work in progress - and opinionated.
I'm sharing it mid-stream in case it can help others work through similar questions for testing Booster apps, or better understand Booster's currently undocumented built-in testing tool.
(I think) I'm fairly skilled in creating event-driven applications, working with Booster, and creating tools in JS/Typescript, but I'm relatively new to writing tests.
Most of this work is based on a common-sense approach but if there something critical you think I'm missing in my thinking or approach, please let me know.
Currently, I'm sorting the details for -
- the levels and types of testing that make sense for a Booster/EDA app
- the helper utilities that can help support, standardize, and simplify tests
- better documentation of everything going on here
I'm doing this next across some real-world projects. My hope is to update this repo as I learn what works best in practice.
In the mean-time, please see the notes below for a high-level overview and dig into the code examples for specifics.
- Repo updates since initial publish
- Goal is to provide patterns and utilities to more quickly and reliably test Booster apps.
- This repo example is using Vitest for testing, but this can be updated to another testing framework.
- Using Booster's
application-tester
package across all examples to simplify testing. - Includes custom updates
to Booster's local-infrastructure 'test-helper' package to allow the
Booster
application-tester
to be used for both local and deployed tests. (The current Booster package functionality for local testing is incomplete). - Includes opinionated code-examples and helper utilities to test opinionated ideas of the expected behavior of an application and the code hygiene of Booster elements (e.g. commands, entities, etc.).
- Also includes helper utilities that abstract test creation substantially, inferring as much data and intention as possible from the tested files themselves via code or special @ notation. (The jury is still out what level of this is useful (e.g. don't want to accidentally create self-fulfilling tests).
The things you'll want to bring into your own project to replicate this testing approach.
- dependencies and 'test:*' scripts:
/package.json
- Booster config setup:
/src/config/config.ts
(updates to 'local' & 'test' environments) - testing framework config:
/vitest.config.ts
- testing constant references:
/test/constant.ts
- testing global startup/tear-down:
/test/globalSetup.ts
(backup/restore datastores for local testing) - testing helper utilities:
/test/test-helpers/*
(utilities to simplify/standardize tests) - testing JWT key:
/keys/testing-public.key
(to decode JWTs created by application-tester) - 'framework-provider-local-infrastructure' custom 'test-helper':
/test/test-helpers/custom-local-test-helper/*
(in-app 'package' override until Booster package completes functionality for local testing)
- Pull together useful patterns, utilities, and code examples to quickly and confidently test Booster applications.
- Clone repo
- Install dependencies
- Perform steps in Running Tests: Testing Local Environment below
- Take a look at -
- example command
- example command test #1, with inline methods that use
application-tester
- example command test #2, that does same work using helper utilities
- example command test #3, that uses helper utilities and also infers test data from source file
- example command test #4, that fully automates test creation via source file @comments and helper utilities
This application is purposely simple, but complete with common elements like roles, validation, handler processing, event-handlers, and projected read-models to test.
Sorely lacking more comprehensive documentation on the details yet, but take a peek
at src/commands/order-snack.ts
and src/commands/order-cocktail.ts
to get a feel
for how the application works. (The funny '@syntax' comments included within
these are part of an idea to
possibly help automate some tests —
jury is still out on whether this makes sense or not).
Testing Local Environment
- in 1st terminal:
- run
npm run start
to start app server - in this terminal, will see output from app server
- run
- once app server running, in 2nd terminal:
- run
npm run test:local
(ornpm run test:local+ui
to open test UI in browser) - in this terminal, will see output from Vitest test runner
- within this terminal, press
a
to rerun all tests, if wanted
- run
Local Testing Datastores
- local testing attempts to backup and restore the event and read model datastores within the
/.booster
directory- currently, this is a simple backup/restore mechanism that can encounter issues (see below)
- if it is critical to preserve local datastores' state before testing, also back these up manually
- backup and restore is done via setup and teardown methods in
/test/globalSetup.ts
- currently, this is a simple backup/restore mechanism that can encounter issues (see below)
- backup/restore lifecycle
- when testing is started it will backup all files within the
/.booster
directory (normally, there should only be 2 files) - while testing is running you can inspect and edit how testing is affecting the datastores (
event.json
andread_models.json
files) - when testing is stopped it will delete the testing datastores and restore the original ones
- when testing is started it will backup all files within the
- when testing crashes (e.g. due to a test error), the teardown methods do not run and both the testing and backup datastores are left in place
- if you start testing again, the setup methods will reproduce all of these files, creating more backups and not being able to successfully restore the original datastores
- in these cases you'll need to eventually clean up, and optionally restore, the local datastores manually
Testing Deployed Environment (AWS)
- run
npm run test:live
- will deploy test stack to AWS
- if make changes to code
- in new terminal run
boost deploy -e test
- in original terminal (where tests running), press
a
to re-run tests
- in new terminal run
- when cancel process in original terminal, should nuke deployed test stack
Source File Requirements
There are a few things tests currently need or can use in Command source files:
- an
tid
input parameter- this is currently required for tests to find the entries they created in the datastores
- it should be of type 'string'
- it can be an optional input in the Command's constructor
- example
- optional @syntax comments that can inform testing automation
-
This repo uses Vitest to run tests - but that is not required. Vitest has a syntax very similar to Jest or Mocha+Chai, but runs tests more quickly and offers features like HMR. If you'd prefer to use Jest or Mocha+Chai, you should be able to utilize most of the patterns and utilities with minimal revisions.
-
It also utilizes Booster's
application-tester
package to simplify testing and normalize test code for different environments (e.g. local & AWS). -
It includes a locally linked, customized version of the 'test-helper' from the
framework-provider-local-infrastructure
package with updates to put in in parity with the 'test-helper' methods afforded by other provider packages (e.g. AWS).
I'm still trying to determine what level of testing is useful/necessary in a Booster application. E.g True unit testing via executing imported classes? Pseudo-unit testing via testing effects? Specific integrations testing?
My understanding and appreciation are certainly limited by my current testing experience but I'm operating on a few assumptions at the moment:
- the Booster framework appears to have good unit/integration testing coverage of its core functionality
- if we can rely on these to ensure the framework is working as intended (e.g. commands processing successfully, events reducing into entities, entities projecting into read models, etc.) than the job at hand for us is to test:
- the intended behavior of the application and its processes
- the code hygiene of the application's parts (i.e. are we following a desired practice like input validation?)
- we can test intended behavior/processes via e2e/functional tests
- if e2e/functional tests can be created more quickly and reliably, we may not need to create (as many) unit tests to confirm the functionality of the individual parts
- we can test code hygiene via pseudo-unit tests
Again, this is definitely an area I'm operating on some shallow knowledge and big assumptions, but hopeful there is leverage to be found that can help make testing quick and part of the creation process, while still being reliable.
For testing, I'm trying to think backward from the responsibility and/or expected effect(s) of each element in the system to determine what assertions and expectations are necessary. The goal is to sort out what's the least we can get away with testing (above and beyond the framework testing that's already occurring) to confirm expected functionality and behavior.
Legend
- ✅ = example and utility added
- ⏩ = will likely be derivative of an existing example/utility
- 🚧 = working on this currently
- 🤔 = not certain yet if this is needed; may be accounted for in Booster framework infrastructure tests
Note: 'process' in this context is something that can be initiated by an actor (command) or on a schedule (scheduled-command), may have 0 or more event-handlers to perform its work, and results in some expected change in the system (i.e. a request and activity that may span more than a single handler).
- 🚧 should perform correct authorization OR correct schedule
- 🚧 should accept specific parameter(s)
- 🚧 should perform certain work
- 🚧 should save specific data to entity(ies)
- 🚧 may make specific data visible from entity(ies)
Note: finished building out items below and currently re-evaluating which of these are relevant to asserting/testing expected behavior or code hygiene vs. testing the reliability of framework.
- ✅ should perform correct authorization
- ✅ 🤔 should accept specific parameter(s)
- ✅ should fail when required parameters are missing
- ✅ should succeed if submitting only required parameter(s)
- ✅ should fail if parameters values are empty ('')
- ✅ 🤔 should fail if parameters are invalid type
- ✅ should perform certain work
- ✅ should register specific event(s)
- ? should be able to receive same command repeatedly or specific number of times
- should be called at specific time(s)
- ⏩ should accept specific parameter(s)
- ⏩ should fail when required parameters are missing
- ⏩ should succeed if submitting only required parameter(s)
- ⏩ should fail if parameters values are empty ('')
- ⏩ should fail if parameters are invalid type
- ⏩ should perform certain work
- ⏩ should register specific event(s)
- ref: https://github.com/boostercloud/booster/blob/main/packages/framework-integration-tests/integration/provider-unaware/functionality/scheduled-commands.integration.ts
- should be called when a specific event is emitted
- ⏩ should accept specific parameter(s)
- ⏩ should fail when required parameters are missing
- ⏩ should succeed if submitting only required parameter(s)
- ⏩ should fail if parameters values are empty ('')
- ⏩ should fail if parameters are invalid type
- ⏩ should perform certain work
- ⏩ should register specific event(s)
- should be able to receive same event without duplicating work
- should be able to process events out of order
- should have specific parameter(s)
- ? should update specific entity(ies) (not sure yet if knowledge of this should really be an event concern)
- should have specific parameter(s)
- should reduce specific event(s)
- ? may update specific read model(s)
- ref: https://github.com/boostercloud/booster/blob/6448db061ba7d11bd91bbd6525e4b646fb8205a9/packages/framework-provider-local/test/helpers/event-helper.ts
- ⏩ should perform correct authorization
- ⏩ should accept specific parameter(s)
- should project specific entity(ies)
- should project entity's public data
- should NOT project entity's private data
- ref: https://github.com/boostercloud/booster/blob/6448db061ba7d11bd91bbd6525e4b646fb8205a9/packages/framework-provider-local/test/helpers/read-model-helper.ts
Included are a number of helper utilities to help simplify and standardize tests. These are still maturing, and range from generic utilities to test-specific utilities.
Note: some helper utilities abstract the test creation substantially (e.g. within helpers-command.ts
) and attempt to infer as much data as possible from the tested files themselves. Jury is still out what level of this is useful (e.g. don't want to accidentally create self-fulfilling tests).
@syntax comments are optional for test automation.
Command: Registered Events @comments
- must be first arguments in event constructor
- must be in this order:
@requiredInputs
: input variables required to trigger event@aReducingEntity
: an entity that will be updated by event (required to lookup event in datastore)
Command: Work to be Done @comments
- can be any where in document
- can include any unique (within document) two-digit key
- require:
@work00
: brief description of work to be done@work00-inputs
: input name and value (e.g. { fruit: 'apple', drink: string })- can use explicit value or generic (e.g. string, number, boolean, id) to generate fake values
@work01-entity
: an entity that will be updated by event (required to lookup event snapshot in datastore)@work01-shouldHave
: value to test if work was done- can be
true
orfalse
if want to test yes/no of work done - can be an array of one or more strings and/or numbers to test if all values exist within result
- example
- can be
Pseudo-changelog of updates to repo since initial publish - helpful if you cloned an earlier version and want to check what's new.
04-25-22
- Updated
test-helpers/helper-readmodels
- returned list of items instead of boolean test
- removed fixed inline filter for
tid
value withinid
field -
have only tested these with local provider thus far
- Updated
test-helpers/helper-misc
- add utility to test if string is JSON or not
- Updated
test-helpers/helper-commands
- updated
wasWorkDone
method to account for stringified JSON values - added 'shouldNotHave' option to
wasWorkDone
method
- updated
04-24-22
- Added
test-helpers/helper-readmodels
- stubbed out initial methods for testing data projected into read models
-
have only tested these with local provider thus far
04-21-22
- Updated testing
id
input parameter- to use string instead of UUID to make it more flexible for user input in testing
- renamed from
id
totid
to help avoid future possible conflicts
- Updated notes
- added "Source File Requirements" section with notes about currently required
tid
in commands - added notes to "Testing Local Environment" about methods that backup/restore local datastores in
/test/globalSetup.ts
- added "Source File Requirements" section with notes about currently required
- Updated
test-helpers/helper-commands
- fix
wasWorkDone
andwasEventRegistered
methods to utilize atid
value manually set in command request - allow multiple values to be used for Work to be Done
shouldHave
values (true|false or array of 1+ string/number values)
- fix
04-20-22
- Added
start:testing
script and comment to package.json to utilize MODE=test variable for Vitest tests. - Changed
@work00-result
syntax to@work00-shouldHave
- Normalized input format/syntax for command inputs and work-to-be-done inputs
- Updated
test-helpers/helper-commands
- update
getRegisteredEvents
utility regex to better match various code patterns. - update
getWorkToBeDone
utility to use '-shouldHave' syntax - add
getWorkToBeDone
errors if a @work comment set is incomplete - update
wasWorkDone
utility to search across nested record data for '-shouldHave' value - relabel 'parameter' to 'input' across utilities and automation @syntax
- update
- Updated
custom-local-test-helper/local-queries
- filter results by kind ('event' v 'snapshot')