IDDD in JavaScript - Part 2: Tests and Decisions

Implementing Domain-Driven Design – In JavaScript! #

Finally, JavaScript #

So, in part 1 we built the original implementation. Now let’s start porting!

There’s a git repository with the “final” solution and all the intermediate steps available as branches here. You’ll see me say git checkout step1 and so forth at many places throughout this blog series – following along is all the fun!

username$ git clone https://github.com/davidfaulkner12/iddd_js.git

I’m limiting my scope here to running the same tests against the new version of the system, and making them work. Of course, this assumes that these tests do exist. Let’s dig into the Java to find them if they do!

Looking at the build.gradle file, there’s nothing obviously an “integration test” in there, which is disappointing, but it does make our life easier.[footnote1] For sanity’s sake, let’s constrain our view to iddd_identityaccess at this point. Looking into src/test, we can see a lot of JUnit files. I thought a lot about what to do next, and decided that the doing a straightforward port of these tests would be my starting point.

Laying the Foundation #

In order to set up a testing framework for this, I decided to use mocha and chai – these have generally easy one-to-one equivalents to JUnit. Here’s the code that I used to set up my project initially.

# Remember that if you cloned my git repo you don't need to this.
davidwf$ git init
# Copy in .gitignore file
davidwf$ git add .gitignore
davidwf$ mkdir iddd_identityaccess
davidwf$ cd iddd_identityaccess/
davidwf$ npm init
# Make sure you set your test script to "mocha --recursive"
davidwf$ npm install
davidwf$ npm install --save-dev mocha chai

From here, I can actually write code! Now there are more decisions to make.

  1. I collapse the package structure: com/saasovation/identityaccess/domain/model/identity/User became domain/user.js, and the corresponding test when into test/domain/userTest.js [footnote2]

  2. I decide to use ES6 classes, which was actually not what I wanted to do originally. I’m a big fan of using JavaScript’s innate prototypal inheritance and have been trying to write code that takes advantage of it as much as possible. However, porting Java code without using the class keyword felt incredibly Sisyphean.

I wrote the simplest possible thing that I thought would work – a test that said I could create a new User, with essentially an empty user class. You can find this here by checking out the step 1 branch:

git checkout -f step1
dfaulkne:iddd_identityaccess dfaulkne$ npm test

> iddd_identityaccess@0.1.0 test /Users/dfaulkne/ddd/IDDD_js/iddd_identityaccess mocha --recursive

User
  ✓ Should construct


1 passing (10ms)

Woohoo! Now let’s port the UserTest.java class to javascript.

Porting our First Test #

Looking at the first JUnit test method, we see the following Java:

public void testUserEnablementEnabled() throws Exception {
    User user = this.userAggregate();
    assertTrue(user.isEnabled());
}

We’ll port it as simply as possible to Mocha and not worry about the fixture:

it ("Should be enabled", function() {
  // TODO! let user = fixtureUser()
  assertTrue(user.enabled);
})

Looking further down, we get to the next one:

public void testUserEnablementDisabled() throws Exception {
    final User user = this.userAggregate();

    DomainEventPublisher
      .instance()
      .subscribe(
        new DomainEventSubscriber<UserEnablementChanged>() {
          public void handleEvent(
            UserEnablementChanged aDomainEvent) {
              assertEquals(aDomainEvent.username(),
                user.username());
              handled = true;
          }
          public Class<UserEnablementChanged>
            subscribedToEventType() {
              return UserEnablementChanged.class;
          }
      });

    user.defineEnablement(new Enablement(false, null, null));

    assertFalse(user.isEnabled());
    assertTrue(handled);
}

More design decisions here! I’m going to try to let the JavaScript API design be driven by the tests.

1) Anonymous inner classes in Java are easily translated to a simple lambda function in JS.
2) I don’t know exactly how I’m going to handle event dispatch, but I’m definitely NOT going to do it by class.

Here’s the first pass at this that I wrote.

it ("Should publish an event that we can listen to when disabled", function(done) {
  let user = fixtureUser()

  DomainEventPublisher.subscribe(
    /*TODO ...*/null,
    (aDomainEvent) => {
      aDomainEvent.username.should.equal(user.username)
      done()
    }
  )

  user.defineEnablement(new Enablement(false, null, null))

  user.enabled.should.be.false
})

You can see how I decided to port all the other tests by checking out step1-1

davidwf$ git checkout -f step1-1

With all the tests ported, I’m going to do some quick and dirty hacks to stub out some stuff just to get it working. I want to build stubs and see how things work out without committing to design decisions before having some working tests – at this early phase in pursuing this port, I don’t feel comfortable committing to anything more sophisticated.

// TODO
let fixtureUser = () => { return new User("joe") }
let DomainEventPublisher = {
  subscribe(whatever, callback) {
    console.log("Subscribe to some event")
    setTimeout(callback, 20)
  }
}

let Enablement = class {}
let ContactInformation = class {}
let FullName = class {}

What’s the rationale behind doing this? Making sure our tests are sane. Prior to these stubs, we were getting lots of reference errors, etc., etc. Now, we actually get errors like “user.username is not defined”, which is actually a useful message. With these stubs in place, I get a list of failing tests, which I can then start to fix, one at a time. You can see this intermediate step by checking out step1-2 – a big set of failing tests that are waiting to be fixed.

git checkout -f step1-2

Kent Beck says to leave a broken test so you know where to start when you come back, so come back next time and watch me implement the entire domain in 27[footnote3] lines of code!

[footnote1]: In a future post, there’s something that at least is kind of like an integration test for the REST services.
[footnote2]: I would totally have made it parallel and included a src directory again if I could start over from scratch, but it didn’t seem important enough to fix.
[footnote3]: Well, maybe a few more than that.

 
3
Kudos
 
3
Kudos

Now read this

Chasing S-Curves: Math and Personal Growth

Getting Better Gets Harder # Narratives are really important to me, and I’m always looking for analogies between different experiences in my life. I just started at a new client this week, and had a very interesting conversation about... Continue →