Learning Web 06: The beginning of project structure

Last time I learned a bit about esbuild and then moved on to try working with the svg element in HTML5. I had originally intended to work with a graph library instead of designing my own but found that it might be difficult to automate for testing. The reason why is that the individual elements in the svg that it created didn’t have very good names to look for. There were also a lot of levels to the objects created so it would be more difficult to find those elements through xpath addressing, or I thought it would be not having really done that before.

I think I’m happy with the decision to roll my own that will have more helpful names. It’s going to teach me a lot about how to do this stuff trying, and the end result might be something that others can benefit from. It’ll be pretty rigorously tested so maybe it’ll be something I can market to teams that might need such.

But I’m setting that aside a bit today to talk about what I accomplished in the last couple days regarding how to set up a project and begin the process of engineering a real component. We won’t get very far, but we’ll cover some important stuff.

Packaging

One of the first issues I ran into while doing the last week of learning was that I need to “package” a typescript project into a javascript “bundle”. This is basically a build process and in fact the typescript compiler is going to translate the typescript into javascript (I believed it was like Python’s types and it would appear that’s not the case). There are a bunch of options available, but esbuild came off as the better one for me. I can’t really say why I decided that (it was a few days ago) but it’s not really a hard decision yet anyway. At any rate, one guiding part of my decision was finding some directions to follow, which I did.

I basically just followed that blog to get an initial setup going.

Unit testing

I’m a major proponent of unit testing. I like to see most stuff covered to where you’ve got a significant portion of your code covered by automated unit tests. There’s a lot of similar minded developers so of course there’s a decent unit test framework for pretty much any programming language. Javascript and Typescript are no different and the tool I found for that job is jest.

To guide me in setting that up I largely used this blog. It mostly worked but it took me a while to find the place where the author tells you to add the export on the function it describes. I fully expected that was the problem but was stubborn about not applying it without direction and it’s not clearly pointed out in the blog article. I did some other slight adjustments and actually put the function and test in differently named files because I already had an index.

Since I basically just followed the directions with some minor adjustments I won’t dig into this a bunch here. The changes will be included later when I show everything.

Mocking

I like mocking libraries and use them a lot when I’m allowed to. I see no reason to deny myself that satisfaction in my own project. I feel like it really speeds up test development and in a way that actally makes them cleaner. Learning how to mock is a bit of a step, perhaps, but the end result is like unit testing: pretty clearly useful.

In python the mocking library has a MagicMock object that can be fed a class definition and it’ll generate the same interface with everything correctly stubbed out to be a mock (tracks calls, provides declarative interface, etc). I don’t have to define the mock’s functions or even declare them like in C++’s trompeloeil. MagicMock is a great time saver and generates better results in my opinion.

It took me a while but I found something that was near enough that is modeled by something from .NET I gather: moq.ts. This library appears to work pretty great, though I haven’t done much with it yet. I did find the documentation for it a bit harder to understand than some others, but it does seem to be complete. I was able to use it to write a quick test that demonstrated that a mock observer is notified when an object is added to a backend instance. So when my Graph class has a node added to it any IGraphObserver that has been registered with it will get a nodeAdded message.

An IWhat?

This is a brief interlude into design space that I’ll be talking more about later. For now, I didn’t find any signal/slot or event mechanism for general classes in typescript. When I searched the web I found instead a bunch of tutorials on the “Observer Pattern”. This is a pretty basic pattern, which is a great introduction to them if you’ve not used them yet, and I already knew it. I looked up naming conventions in TypeScript and they are similar to Python. I looked up how to make classes and learned that TypeScript has interfaces, though they’re a bit funky compared to what I’m used to, and how all that plays together. I came up with the following:

There’s going to be parallel view classes for the graph models that will implement the IGraphObserver interface. So when something changes in the graph that gets reflected somehow in that view. In unit tests this interface serves as a seam that allows us to inject information from the test data and/or sense output from another unit. I’m going to do that largely with mocking.

A function specification

In this entry I’m just going to test a single function: createNode. This function:

I want to test this function two different ways:

Test first

I think tests should be written first and then you should write the code that passes them. This isn’t always practical or possible to do as an absolute, but in general I think it is useful to do and provides a lot of hints for going forward. Even if it’s not possible to start coding the test without at least some sort of stub to start on, you can write out the “test protocol”: a more textual description of the test that may be part of submitted documentation for regulatory controls but on most teams might not even get written down. A more basic process though, if you can use it, is to just start writing test and when you can’t build it you make only enough that you can until you have a first test that fails and then you make it pass.

I won’t repeat that process here, but that is the one I used. Here I’m just going to show you the test I wrote:

import { Graph, IGraphObserver, Node } from '../src/graph';
import { Mock, It, Times } from 'moq.ts';

Here I have imported the classes and interfaces from my code involved in the test and then also some utilities from moq.ts that I’m going to use.


describe("node creation and destruction", () => {
	test("create node adds it", () => {

This is how you define tests in jest. I’m not unfamiliar with ones like it that I’ve used but I don’t know the details. These tend to have a lot of expressive power and I noted later that there’s more verbs I could have used that would have been better maybe. My code is probably not idiomatic. Basically, I’m defining a test “suite” that has this test in it. In reports these strings I’ve supplied will be provided to contextualize any failure or success (sometimes you report those also).

		let graph = new Graph();

		expect(graph.nodes.length).toBe(0);

		let node = graph.createNode("derp");
		expect(node.name).toBe("derp");
		expect(graph.nodes.length).toBe(1);
	});

So pretty basic. I’m just checking that my node gets created and added and that it was created with the name I provide. If you are not used to this style of assert pattern it can take some getting used to but I’ve seen it before as well. Instead of saying something like assert(graph.nodes.length == 1) I’m giving a more natural language style description: “I expect graph.nodes.length to be 1.” This also is going to reflect in test reports as a more natural output, or at least can be used to do so.

	test("create node is observable", () => {
		let graph = new Graph();

		const mock = new Mock<IGraphObserver>()
		   .setup(instance => instance.nodeAdded(It.Is((node: Node) => node.name === "derp")))
		   .returns();

		const observer = mock.object();

I still need to learn this library and this took me a while to figure out. There’s a LOT going on here. That’s something I really like about libraries like this is that they simplify a lot of stuff but you do need to get to know them a might bit more than I do at this point. It’s very similar to what I’ve used but I also ran into some stuff.

I also need to learn about java/typescript scoping still and my use of const vs. let above is probably incorrect. I don’t mind publishing it this way because this is my learning log, not a “this is how you do it.” I am, however trying to discuss some of the elements I’m running into that are common across systems such that it’s allowing me to succeed here. That’s what I’m hoping is useful to you, the reader–if you exist.

What I did above is use the moq.ts library’s “automock” feature to create a mock based on my IGraphObserver interface. I then tell it to expect a call and what to do with it. The setup function expects an expression that tells it how to evaluate if this is the call being matched. Here I’ve said that the call to match is one in which nodeAdded is called with a parameter that’s name property is “derp”. If you just read out everything that’s in the outer () you can kind of see it:

“Using instance, match a call on it to nodeAdded in which the parameter passed is a Node with the name ‘derp’.”

These sorts of expressions are similar to others in Python and in C++ with trompeloeil. I need to learn the language features being used here to really understand what’s behind all that, but that’s going to be a while. In both other languages I’m more deeply familiar with these are deep, metaprogramming level constructs that can be difficult to untangle and I’m simply not ready. But, my familiarity with these sorts of things allowed me to untangle it enough to figure out what I needed to change. The one thing that took me the longes to figure out though was that…

The next call to .returns() is absolutely necessary or nothing works and you get really confusing error messages. “What do you mean object() isn’t no function call?”

The .returns() closses the expectation. When you first construct the mock you get a Mock. Then when you call setup you get a builder class that sort of “opens” the expression. There may be other ways to close it, but most of the examples were all using .returns(), they just were all returning something and I’m not. You have to do this anyway or the end assignment to mock isn’t a Mock but this halfway constructed match expression that does none of the things you expect of it.

		graph.addObserver(observer);
		const n = graph.createNode("derp");

		mock.verify(instance => instance.nodeAdded(n), Times.Once());
	});
});

Here at the end is the meat of the test. I add the observer, make my call, and then check that the expected mock call was made. Here I made the easier match of just checking that the exact object I passed in was also the one received. I could have done so above as well if it was the same object all three times, but I wanted to verify I could do both so that I can define my mocks to generically match but then later test that the match happened to a particular object. The match is more of an allow and the verify is the actual assertion.

Passing code

The code that passes is trivial. Typescript interfaces were a bit odd to me but they’re making sense with what little I’ve gathered of the language. The classes themselves were pretty normal for any object oriented language. I could have made my property private but I didn’t and that makes it public I guess. I will learn this stuff more in bit … I’m mostly playing right now.

export class Node {
	name: string;

	constructor(n: string)
	{
		this.name = n;
	}
};

export interface IGraphObserver {

	nodeAdded(node: Node): void;

};

export class Graph {
	nodes: Node[] = [];
	observers: IGraphObserver[] = [];

	createNode(name: string): Node {
		let node = new Node(name);
		this.nodes.push(node);
		for (const observer of this.observers)
		{
			observer.nodeAdded(node);
		}
		return node;
	}

	addObserver(observer: IGraphObserver): void {
		this.observers.push(observer);
	}
};

Don’t use that as a pattern for anything. I’m doing a very rough initial implementation of the Observer pattern but there’s a bunch that’s missing. I may not even finish and opt instead to use someone’s library or whatever. But this is a pretty basic example that got me a complete build system that is using unit testing with mocking support.

Code availability

Soon. I want to make a complete setup first. So next step is documentation. Then a minor UI and full integration with test suite so I have everything working on one at least one thing.