Understanding ARK Core: Learning to Read Tests

We’ll go further into ARK Core v2 by learning how to read tests. Tests, in addition to being very helpful in writing code, are very helpful in reading code. We’ll focus on the core-api tests, learning more about APIs and how to test them.

This column is Part 2 of Understanding Code: ARK Core, and builds on previous entries in the series. If you haven’t read Part 1 I recommend doing so before continuing, unless you have previous development experience. As usual, find me on Twitter and Twitch @ceibaweb, and show your support for this series by donating ARK at: AJAAfMJj1w6U5A3t6BGA7NYZsaVve6isMm

Why Tests?

After a few years of teaching myself how to program, I’ve arrived at the strong belief that traditional programming curricula contain one major flaw.

The vast majority of classes proceed in the same way: the building blocks of programming syntax are taught first, then you’re taught how to put everything together. At the end of the class, squeezed in between super-advanced concepts, you’ll find the testing section, if a testing section exists at all.

On the one hand, I understand the surface appeal of this approach. Testing involves thinking about code at a high level, and accepted wisdom is that it’s better to teach the nuts and bolts before teaching how to build a car.

However, when you think about programming as solving a problem, and when you think about testing your code as demonstrating that your solution solves the problem, it becomes clear that testing is perhaps the most important skill you can learn. If you can’t prove that your solution is effective, how can you be sure you’ve solved the problem at all?

There’s one more reason I feel that tests are super-important to learn as early as possible. Even as a non-developer, when you think of any program you think of the problems it solves. Spotify streams music, Gmail helps us keep track of the emails we’re ignoring as they pile up in our inbox, and so on.

The best tests deal with those problems and their solutions directly, in a way that’s impossible for actual code to replicate. There may be no single point in Spotify’s code where music is streamed — there are probably separate points in which the song is selected, fetched from the database, converted into a streamable format, sent to your application, and played.

But if I were writing a test for Spotify, and I wanted to make sure that the test was helpful no matter how much the underlying code might change, I’d have to write it as simply as possible. I’d probably write something similar to:

I just clicked on Nickelback. Am I hearing the worst music ever recorded?

That way, I can change the underlying code as much as I want. So long as my clicking on Nickelback leads to my computer’s speakers being assailed by the worst music ever recorded, I know that my code is working.

I’m kidding, y’all. Nickelback is awesome. How can you not like this?

As you might remember from the previous installment, we’ll be tackling two separate but closely related subjects over the next few columns in the ARK Core series. In addition to learning the fundamentals of the ARK API, we’ll also take some time to learn the fundamentals of using tests to understand code.

This column is almost exclusively dedicated to the latter goal; we’ll talk about ARK API only as much as necessary to understand how the tests are working. In the next column, armed with a better understanding both of APIs and of tests, we’ll dive full-on into the ARK API — how it works under the hood, and what packages we’ll need to understand to go further into the blockchain itself.

Fortunately for us, we won’t encounter any test-day jitters: other really smart programmers have already solved these tests for us. Instead we get to cheat off the tests they’ve already solved for us, in a Jeopardy-style process in which we’ll match each answer (ie. each test) together with the work required to reach that answer successfully (ie. the code behind the test).

An Introduction to Testing

Opening the __tests__ within the core-api directory, this is what we see:

A quick note on why lots of folders are surrounded by underscores: by default, when you run one of the Jest commands that we’ve seen in in both package.jsons so far, Jest will automatically look for a __tests__ folder and run any JavaScript file that it finds within them as a test.

You might think that the underscores look weird. They do; that’s the point. The odds that you would name a folder with double underscores in your own project is slim to none. When Jest encounters a folder named __tests__, this is a pretty safe bet that the code inside represents tests that should be run and not some unrelated part of your application.

To my knowledge, neither __fixtures__ nor __support__ does anything special on Jest's end. It seems likely that Ark chose this folder structure to differentiate these folders from test-containing folders, and to keep the folder names consistent across different core packages.

In any case, the tests we’re looking for are contained within the v2 folder. Let's take a closer look.

We see a single file, utils.js, and a folder named handlers. We'll return to utils.js in a moment, but for now let's visit the handlers folder.

There they are — the tests we’ve been looking for.

We’ll start from blocks.test.js. Remember that we're working from the outside in — these tests won't cover how to actually create blocks, but they'll cover the ability for external developers to fetch information about blocks from the ARK API.

Demystifying Our First Test

Here’s a screenshot from the first section of the blocks.test.js file:

Scary! Strictness, dot notation, and describe blocks, oh my!

Fear not, we’ll walk through this intro segment until we’re totally comfortable with how it works. Once we get a good grasp on what this test does and what code it covers, we’ll be able to get more into the weeds of the program itself.

We’ll ignore use strict for now, as it's not vital for our purposes. If you want to know more about it, this column is a good place to start.

The second line of code is a require statement. This tells our program to fetch the code at a specified location and treat it as if it were written in this file, executing everything it finds there immediately. The double periods mean "move up one folder", so this line is saying "move up two folders, go to the __support__ folder, and execute the setup.js file that you'll find there."

We don’t have the requisite background to fully understand what’s happening in the setup.js file, so we'll leave it for later. The SparkNotes is that this file sets up a "test blockchain" with dummy data that we can use to make sure our tests pass without having to connect to the actual ARK blockchain. This makes testing far easier and faster, as we won't need a Internet connection to run our tests and we won't be bothered by any problems that might be affecting mainnet or testnet. It won't setup a fully functioning blockchain, but it'll give enough data to our program to where our API won't know the difference, and will be able to handle our tests without a hitch.

Next, we load the utils.js file we saw earlier, as well as a genesisBlock that simulates a single block of our test blockchain.

Following that is where our tests begin in earnest. Here’s a screenshot of just the test:

You’ll see a line with a function called describe, which is called with two parameters. The first parameter is just a description of what the tests within the function do — in this case, API 2.0 - Blocks tells us that these tests cover the blocks functionality of ARK Core API 2.0.

The second parameter is what’s called an anonymous function. Like riding to town on a horse with no name, an anonymous function is just a function with no name, intended to be used only once: at the spot where it’s defined. You can distinguish it by this pattern: () ⇒. Any arguments that this function takes are included within the parentheses, and the arrow (also known as a fat arrow, but let's not be mean) tells us that this is an anonymous function.

If you wanted to write an anonymous function that took two numbers and returned the result of adding them together, you could write that like this:

(number1, number2) ⇒ number1 + number2

In the case of our test, our anonymous function takes no arguments, so we can just write () and be done with it.

In sum, the describe function is used to separate your tests into distinct units. Its usefulness is mostly semantic, in that it makes our test suite easier to read. If there's some code that's only useful for a given section of tests, you can include it within your describe function as well, but its primary use is ease of comprehension.

After the first describe function, we have another describe function — but this one begins getting into the good stuff.

What Is An API?

We talked a little bit about APIs in the last column, and a full overview of what an API is goes slightly beyond the scope of this series. However, I’ll provide a hands-on example of an API, because our example will come in handy in a minute.

Let’s say, for example, that we run a custom cake shop. We want to create a website where our customers can look at all the cake recipes in our inventory, and submit their own cake recipes for us to craft into delectable masterpieces.

API stands for Application Programming Interface, and it’s got that name because an API is an interface that you can use to program an application. Put another way, an API is the layer between our bakery’s database, where we store recipes for all the cakes we’ve ever baked, and the outside world, which may want to see our cake recipes or even add recipes of their own.

So, if we were to create an API for our custom cake shop, what would it look like?

We’d want our customers to be able to:

  • Browse through every cake recipe
  • Read a particular recipe
  • Edit recipes they’ve submitted
  • Add new recipes
  • Delete recipes they’ve submitted

Conveniently, the abilities we want our API to have (Browse, Read, Edit, Add, Delete) form a delicious, gluten-y acronym — BREAD!

Many APIs that you find in the wild will follow this same convention. You’ll also see this convention defined as CRUD (Create, Read, Update, Delete). BREAD and CRUD are effectively synonymous, and describe the same basic functionality. But, in addition to lumping Browse and Read together, CRUD is, well, cruddy. BREAD is fluffy, warm, fragrant, and very cake-related.

Now that we know what an API is, and are perhaps a tad hungrier than before, let’s see how our bakery stacks up against ARK Core.

See? Delicious, delicious cake and BREAD. And people think APIs are hard.

Cakes and Blocks

Our second describe function has a somewhat opaque description:

'GET /blocks'

In order to understand what this is referring to, let's think about it in terms of our bakery.

The B in BREAD stands for Browse, which for our bakery represents the ability to browse all recipes.

Now, to apply the same logic to ARK API, all we need to do is replace cake recipes with the specific resource we’re analyzing — in this case, blocks.

This leads us to the conclusion that, for the B of our BREAD API to work as intended within the context of ARK API, we need to be able to Browse through all blocks.

But what about ‘GET’?

‘GET’ is an HTTP method. Mozilla’s developer documentation offers a concise summary of HTTP methods and their role in the greater Internet infrastructure, but there are only two methods that we need to worry about for ARK.

  • GET is the most common method on the Internet, and it’s essentially a request to GET information from a web server. If you go to <ark.io> in your browser, for example, your browser is sending a GET request to <ark.io>, which the ARK server will respond to by sending you the files necessary to load the ARK homepage. In an API context, if we visit <arkbakery.com/api/recipes>, we'd expect to get a list of all recipes.
  • POST is the second-most common method you’ll find, and it tells the server to create a resource. If we submit a contact form on a website, we’re sending a POST request to the server with the information we filled out on the form (our name, email address, message, and so on). The server will then use that data to create a new submission in our contact form. For our imaginary bakery, if we send a POST request to <arkbakery.com/api/recipes>, we're telling the bakery's server to add our recipe to the complete list of recipes.

Let’s look at our first test again:

With any luck, this code is beginning to make a bit more sense. We see that we’re making a call to the Browse functionality of the blocks API, and we want to receive all of the blocks in return.

Within the test itself (everything within the it function), we're making a GET request to blocks and expecting three things to be true:

  • The response should be successful. In other words, the server shouldn’t give us an error.
  • The response should be a collection of blocks, not just a single block.
  • The response should be paginated. This means that the API should return one “page” of blocks. It shouldn’t return every block that’s ever been mined, because that would be a lot of blocks, and our application might crash if it received all of those blocks at once.

In the next two lines, we’re assuming that the data we get back from our request is an array of blocks. We’re taking the first member of that array and confirming that it is, indeed, a block.

Under the Hood: Inspecting Utils.js

We’re pretty much good to go exploring core-api in more detail, now that we've established how tests work at a basic level. But there's one part that you may still be confused about — what are utils.js, and why are they used so much throughout these tests?

It’s worth exploring in a bit more detail, because this pattern is used throughout the ARK API as well. To keep the actual API handlers light on code and easy to understand, both the tests and the code of ARK API utilize a utils.js file that does the heavy lifting of translating common JavaScript libraries into something more ARK-specific. Let's look at the code behind the test we just analyzed:

Expect Successful

Expect Collection

Expect Paginator

Expect Block

Between JavaScript and ARK

Here is where we start to see how our tests can help us understand the ARK Core internals.

The many calls you see to expect are utilizing a popular JavaScript testing library called, predictably, Expect.js. Recently rolled into Jest as part of its acquisition by Facebook's open-source team, Expect's sole purpose is to check the value you give it against a wide range of useful assertions. Assertion is a fancy word for a check: we check the value and assert whether it matches or doesn't match the value we expect.

You can see this happen particularly in the expectBlock function. When the test we analyzed checks a block to see whether it is, in fact, a block, all of the assertions under the expectBlock function are run against the response we get from the API. If any of those assertions fail, our test will fail and tell us which assertion caused the test to fail. If our block had no ID, for example, our test would fail and tell us something along the lines of: Expected block to have a property ID, but none was found.

So hopefully it’s a bit clearer why all of this work is relegated to its own utils file. By matching the general-purpose Expect library to ARK-specific assertions within the utils file, the ARK developers can keep the actual tests lean and easily-digestible.

And that’s it for tests! In the next column, we’re going more into the code itself, using what we’ve learned here as a guide to tell us what to look for and where to look for it.

Like this column? Got a question? Reach out! I’m @ceibaweb on Twitter, and I livestream coding a few times a week at https://twitch.tv/ceibaweb. Generous parties can fund the continuation of this series via my ARK address at: AJAAfMJj1w6U5A3t6BGA7NYZsaVve6isMm