In the last challenged we learned how to use links through creating a blogging app. If you completed that, this challenge should feel familar. We will be learning testing and validation by continuing where we left off.
In this challenge we will be learning tests, and using TDD to learn how to write validation rules. Some of the learnings will include:
- 1. Navigating Vitest and Tryorama
- 2. Testing CRUD zomes calls.
- 3. Only allowing creation of comments under a maximum charactor limit
- 4. Only allowing editing of your own comments
- 5. Only allow a user to comment 3 times per 24 hours
What is TDD?
Test-Driven Development (TDD) is a coding approach where you write tests for your features before writing the actual code. It works in short cycles: you start by writing a test for a small part of the feature, then write the code to make the test pass.As you continue developing your app, you can also continue testing the code on old tests you wrote, to make sure that you haven't broken something along the way.
4. If you didn't complete the last challenge, run npm start
and have a play around with the current app to understand its components
Vitest is a popular Javascript Framework for testing. It provides a simple setup for creating and running test scenarios, for all sorts of applications.
This command will execute every file ending in .test.ts
within the tests/src
directory (or any of its subdirectories).
To run a specific test or group of tests, you can specify the file name, or folder/folder path containing it
Eg: npm test all-posts
, npm test blog/practice
This is a test at its simplest. When the file is executed, every call to assert
validates the truthiness of the first argument.
If the value is truthy, the test passes, otherwise it fails.
There are many Vitest methods for creating assertions that you will see throughout this challenge. For more information, consult the Vitest documentation.
On your terminal, you will notice that see a very helpful message detailing that our test failed
5. Re-comment the failing assert inside of the barebones test, and navigate to all-posts.test.ts
and have a read through the file
Here we introduce a more complex test that utilizes Tryorama - Holochains toolkit for running tests.
Try understand the different parts going on inside the file
- Notice how just like our barebones test,
assert
andtest
are the only functions being imported from vitest, everything else is related to tryorama - Notice how inside the file, our test is making zome calls in a similar fashion to how we do on the client.
Similar to how the tests are written inside post.test.ts
, we are going to create scripts to test the Create, Read, Update, and Delete functionality of our comment entry type.
These functions will be used multiple times throughout our tests. It will be helpful to understand how they work.
2. Navigate to comment.test.ts
and create a test and run a test for checking if Agents can create comments on the DHT
- Start with a
test()
function - Create a scenario
- Install the app with the correct path
- Add two agents to the the scenario, and share them on the network
- Make a call to
createComment
- Use
assert.ok(record)
to validate the whether a record was returned (the test succeded) or a record was not returned (the test failed)
Try do it without the hint! If you're struggling, you can also refer to post.test.ts
to see the implementation of a similar test.
You can run npm test comment
to execute your new code
Hint
test('create Comment', async () => {
await runScenario(async (scenario) => {
// Construct proper paths for your app.
// This assumes app bundle created by the `hc app pack` command.
const testAppPath = process.cwd() + '/../workdir/c-2.happ'
// Set up the app to be installed
const appSource = { appBundleSource: { path: testAppPath } }
// Add 2 players with the test app to the Scenario. The returned players
// can be destructured.
const [alice, bob] = await scenario.addPlayersWithApps([
appSource,
appSource,
])
// Shortcut peer discovery through gossip and register all agents in every
// conductor of the scenario.
await scenario.shareAllAgents()
// Alice creates a Comment
const record: Record = await createComment(alice.cells[0])
assert.ok(record)
})
})
This test is a bit more complex. There will be multiple assert
functions for testing each of these functions:
- The creation of a comment (To read comments from the DHT, we also need to create them inside the same scenario)
- Retrieval of a test by its action hash
- Retreval of comments by there corresponding post
Hint #1 - Breakdown of each step
- Create a
test()
function that asserts the creation of a comment - Use
dhtSync
to await the propergation of the entry around the network - Create another
assert()
for testing the zome functionget_original_comment
- Retrieve all comments by their post hash
- Create another
assert()
for checking if the retrieved comments contains the one we created
The assertions in this test should include
- Creation of a comment
- Updating of a comment
- Retreval of the latest update of a comment
The assertions in this test should include
- Creation of a comment
- Deletion of a comment
- Retreval of all comments does not return the deleted comment
Now you have a better idea of testing with Holochain, we are going to use TDD to write some validation rules.
The assertions of this test should include:
- A check that a comment under 15 charactors can be created
- A check that a comment over or equal to 15 charactors cannot be created
As we haven't written the validation rule yet, the test should be failing on the second assert.
The file comment.test.ts
might be getting a bit large, so feel free to create a new comment-validation.test.ts
The file contains many functions for implementing validation rules, however you will notice that in most of the functions, there isn't alot going on.
3. Implement a validation rule to make sure that the content of the comment attempting to be created is < 15 charactors long
Validation rules may seem like they are complicated, but under the hood, they are simple logical checks.
- To fail a validation check, return
Ok(ValidateCallbackResult::Invalid())
. - To Pass validation check, return
Ok(ValidateCallbackResult::Valid)
.
Hint
Modify your validation function to look like this
pub fn validate_create_comment(
_action: EntryCreationAction,
comment: Comment
) -> ExternResult<ValidateCallbackResult> {
match comment.content.chars().count() < 15 {
true => {
let record = must_get_valid_record(comment.post_hash.clone())?;
let _post: crate::Post = record
.entry()
.to_app_option()
.map_err(|e| wasm_error!(e))?
.ok_or(
wasm_error!(
WasmErrorInner::Guest(
String::from("Dependant action must be accompanied by an entry")
)
)
)?;
Ok(ValidateCallbackResult::Valid)
}
false =>
Ok(
ValidateCallbackResult::Invalid(
"Validation Error: Comment is >= 15 charactors!".to_string()
)
),
}
}
The assertions of this test should include:
- A check if an agent can update there own comment
- A check if an agent cannot update another agents comment
Hint
record.action().author()
retrieves the public key of the author of the action. The reason we compare against it instead of Comment.author
, is because we can't trust the value of Comment.author
, -it could've been modified by the client.
pub fn validate_update_comment(
_action: Update,
_comment: Comment,
_original_action: EntryCreationAction,
_original_comment: Comment
) -> ExternResult<ValidateCallbackResult> {
let _record = must_get_valid_record(_comment.post_hash.clone())?;
let author = _original_action.author().clone();
match author == _comment.author && author == _action.author {
true => Ok(ValidateCallbackResult::Valid),
false =>
Ok(
ValidateCallbackResult::Invalid(
"Comment can only be edited by the original author".to_string()
)
),
}
}
The assertions of this test should include:
- A check if an agent can create a comment
- A check if an agent cannot create more than three comments during the test
You will need to use the must_get_agent_activity
function to deterministically retrieve information about previous actions made on the agents source chain.
This validation logic is a bit more complex. Give it your best shot, but if you are struggling, refer to the challenge solutions for help.
And thats the end of it! There are many more validation rules we could create. (Eg: Can't update comments with a length of more than 15 charaters)
Feel free to implement anymore!