firebase

Jasmine Unit Tests in an RxJs + Firebase app: Part 1

Unit testing can be…well….different in an app built on libraries devoted to functional, asynchronous paradigms. AngularFire 2 may well be the epitome of this. There’s a lot to unpack. It’s TypeScript for one thing, not just plain JS. On top of that, add the RxJs library. Firebase‘s realtime database feature is both real-time and NoSql for another thing. It uses WebSocket so all the RxJs stuff really does sit around emitting events whenever data changes in Firebase. Phew! This does not make things plain and simple!

I’ve gathered some experience with it the past eight months in an Ionic mobile app, and here are the tips and tricks I have gathered, collected, stumbled upon, and cringed over. We used Jasmine as the test framework in my application, and these examples do the same.

By no means do I mean the examples below to be prescriptive or otherwise thought of as “the way to do things.” Probably there are better ways. But they have worked for me and my team. I encourage anyone to provide suggestions and feedback in the comments section.

First things first: Setup!

The following snippet offers a good starting point for a basic set of unit tests.

describe("The Alert Helper", () => {

  let alertHelper: AlertHelper;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        AlertHelper,
        { provide: AlertController, useClass: AlertCtrlMock },
      ],
    });
  });

  beforeEach(inject([AlertHelper], (helper: AlertHelper) => {
    alertHelper = helper;
  }));

Line 1 calls Jasmine’s describe function. The string you pass here in argument 1 is what you will see as the first line in any test output — the white line in this screenshot:
JasmineTestOutput

Of course the rest of this is a function or lambda that contains all your unit tests. Calls to beforeEach will predictably happen before each test. The TestBed object is a powerful part of Angular that we’ll cover more in depth as we go, or that you can read about on their site. All you need right now is the basic idea that it allows you some flexible ways to substitute test doubles for dependencies that are not under test in the current test suite.

The Basic Happy Path
After you have the basic setup out of the way, a basic happy path unit test for an Angular 2 service might look something like this.

  it("should create an error alert with a provided message", () => {
    spyOn(AlertCtrlStub.prototype, "create").and.callThrough();

    alertHelper.triggerErrorAlert("This is a message!");

    expect(alertHelper.prototype.create).toHaveBeenCalledWith({
      message: "This is a message!",
      buttons: [
        {
          text: "Ok",
          role: "cancel",
        },
      ],
    });
  });

Line 1 is a call to Jasmine’s it function. In the aforementioned screenshot of test output, argument 1 to this call is the string that will show as the green lines (or red if they fail!).

Line 2 here shows the use of a Jasmine spy. Spies have great documentation on Jasmine’s web site, and are a powerful tool. In this example, we spy on a stub. The reason for providing a stub, but also spying on it, is two-fold:

  1. The stub makes sure no real code executes that we don’t want to execute.
  2. By spying on it we can see if it was called, and what values were passed to it when it was called.

We start the test by defining the spy, so that it is active when we later make the calls through the actual code. As the code executes, the spy will keep all the metadata about how many times it was called, and what arguments were passed to it, and so forth.

Finally in line 6 we use expect to make sure that our spy was called with a particular argument. This argument is an object that represents a basic error alert. This is because the thing we are testing is a simple utility function used throughout the app that takes a string and presents an alert with an OK button. If the buttons in this method were changed, or the code changed the string that was passed in, this test would start failing, indicating perhaps that a refactor went awry or something like that.

The Happy Path for an AngularFire 2 call
Any of you who have already been using Angular and Jasmine will be yawning at this point, so let’s move to something a little more spicy: the basics of testing interactions with Firebase. We’ll cover much more on this subject in part 2 of this series, but here is the happy path test for one of these interactions.

In our application users can view a “post” from another user. The code for this is quite basic AngularFire 2: a simple call to object:

public fetchPost(postID: string): Observable<IPost> {
  return this.af.database.object("/post/" + postID);
}

To set up a test suite for this, we use the following:

describe("The Post Data Service", () => {

  let postData: PostData;

  let objectSpy: Spy = jasmine.createSpy("object").and.callFake((path: string) => {
    if(path.includes("123")) {
      return Observable.of({
        title: "Example Post",
        body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla commodo dui quis.",
      });
    } else {
      return Observable.throw("Invalid path!");
    }
  });

  let afStub: any = {
    database: {
      object: objectSpy,
    },
  };

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        { provide: AngularFire, useValue: afStub },
        PostData,
      ],
    });
  });

  beforeEach(inject([PostData], (postDataInjected: PostData) => {
    postData = postDataInjected;
  }));

Since the call in the code being tested is return this.af.database.object("/post/" + postID);, we have to stub out the af object and the database object (lines 16-20 above) like this:

let afStub: any = {
    database: {
      object: objectSpy,
    },
  };

There’s no need to use Jasmine for this, bare TypeScript is fine. Once we create a barebones stub for these, we point the “object” property of af.database at a Jasmine function spy we created named objectSpy. objectSpy was created in lines 5-14 and is just a fake which returns an Observable of a post if “123” is passed in and an empty Observable otherwise:

let objectSpy: Spy = jasmine.createSpy("object").and.callFake((path: string) => {
    if(path.includes("123")) {
      return Observable.of({
        title: "Example Post",
        body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla commodo dui quis.",
      });
    } else {
      return Observable.throw("Invalid path!");
    }
  });

The part that ties this all together is line 25, where we take advantage of TestBed‘s useValue and substitute our stub in place of the actual AngularFire provider. This is called supplying a ValueProvider.

        { provide: AngularFire, useValue: afStub },

Now we can proceed with a simple happy path test:

it("should return all data from a post when the specified ID post exists", () => {
    postData.fetchPost("123").subscribe((post: any) => {
      expect(objectSpy).toHaveBeenCalled();
      expect(post.title).toBe("Example Post");
      expect(post.body).toBe("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla commodo dui quis.");
    });
  });

And a test for the “not found” case:

it("should throw an error when the specified post ID does not exist", () => {
    let value: Observable<IPost> = postData.fetchPost("456");
    value.subscribe((post: any) => {
      fail("This should never be reached");
    }, (error: any) => {
      expect(error).toBe("Invalid path!");
    });
  });

Objection!
Yes, I can hear your objection already. The above test is tautological. What does it actually test? Well, keeping in mind that this is all for the sake of example and teaching some test methods, my answer is that it tests the code. The function in question may rely on AngularFire 2 and Firebase, but the actual function really does have the following functionality:

a) return a post object if the post ID exists
b) return nothing if not

So these tests actually are important and do test this code. And we actually do want to decouple the code from its dependencies by using test doubles.

What’s in part 2?
For part 2 of this series, I’m open to feedback. I’ll probably post it next week or the week after. My plan is to move on to tests on af.database.list and then try to find any oddities and rough edges that we hit in our own code. But like I said, please comment below if there’s anything burning on your mind.

Basics of fault-tolerant Promises in TypeScript

Recently I’ve been spending some time developing an Ionic 2 mobile app, which uses Angular and TypeScript.

I have noticed a couple places where my team has code similar to this:

someCallThatReturnsAPromise().then( () => {
  someOtherCallThatReturnsAPromise().then( () => {
     someThirdPromiseCall();
  });
}, (err) => {
  this.alertHelper.triggerErrorAlert("Hi an error happened: " + err.message);
});

This works but it suppresses errors. This would only execute the err function if the first call (someCallThatReturnsAPromise()) errors out. Not the other two.

There is a better way! If you write code like this, you may notice a warning from the IDE or your linter saying that you are ignoring the result of a promise. Don’t ignore that helpful message! If you want the app to be stable and fault-tolerant, it is just as easy to chain the promises together correctly instead of nesting them. And you get the benefit of fault-tolerance this way!

The above example can be rewritten as follows:

someCallThatReturnsAPromise().then( () => {
  return someOtherCallThatReturnsAPromise();
}).then( () => {
  return someThirdPromiseCall();
}).catch( (err) => {
  this.alertHelper.triggerErrorAlert("Hi an error happened: " + err.message);
});

What this does is it passes the result of each promise along to the next piece in the chain. That includes any errors. The .catch at the end will catch an error arising in any one of the three calls.

You can use this to your advantage to stop the whole chain and throw an error instead if some condition is not satisfied for continuing. Consider this:

return fetchUserUid().then( (uid) => {
  if(!uid) {
    return Promise.reject("Unable to fetch user right now. Try again later!");
  }
  return fetchProfileForUser(uid);
}).then( (profile) => {
  if(!profile) {
    return Promise.reject("User profile unavailable. Try again later!");
  }
  return profile.fetchUserName();
}).catch( (error) => {
  this.alertHelper.triggerErrorAlert(err.message);
});

In this example, if the call to fetchUserUid fails none of the rest of it executes because the promise is rejected and the catch block immediately executes, which will put an error alert up with the message “Unable to fetch user right now. Try again later!” If the call to fetchUserUid succeeds, but the call to fetchProfileForUser fails, the catch block will execute and put up the message “User profile unavailable. Try again later!”

If both succeed, then the third call to profile.fetchUserAddress() will get called.

In other words, it is called a “promise chain” because it is a chain of events that you have created, but each link in the chain will only execute if the previous one has resolved. That brings up another useful static method: Promise.resolve(). Like Promise.reject() you can use it to easily return a value. It is analogous to the following:

return new Promise((resolve: Function) => {
      resolve();
    });

An example of how you might use this is to gracefully tolerate faults by supplying a default value. Let’s say all you want to do with the previous code is display a greeting at the top of the page. Then you probably don’t need to throw up an error alert on the error case. You can simply put “Hello and welcome!” instead:

return fetchUserUid().then( (uid) => {
  if(!uid) {
    return Promise.resolve(undefined);
  }
  return fetchProfileForUser(uid);
}).then( (profile) => {
  if(!profile) {
    return Promise.resolve("Hello and welcome!");
  }
  return profile.fetchUserName();
}).catch( (error) => {
  this.alertHelper.triggerErrorAlert(err.message);
});

Now the catch block will still execute if an error is thrown but where we used Promise.reject before we now actually pass along a value via Promise.resolve which ultimately will resolve to “Hello and welcome!” for the case where either fetchUserUid() returns undefined or fetchProfileForUser returns undefined.

Hope it helps!

Non-functional requirements are important yet often forgotten

abolla_18076_sm

Most software professionals in my experience lapse into talking about “requirements” while meaning only functional requirements. We seem to forget that there are also non-functional requirements, and that non-functional requirements are usually just as important as functional requirements to the end user or customer. Sometimes the non-functional requirements are more important.

Just to set some context, I would describe “functional requirements” as software requirements that . . .

javalogo

Tackling Map.get()’s null-returning anti-pattern

In the beginning was Dictionary and HashTable, and they returned null when a call to get() was made. And the darkness prevailed over the land. And programmers found many NullPointerExceptions in production and confusion reigned on the face of the deep.

Then lo Map and HashMap were created. And there was great rejoicing, because their performance was greatly improved. And yea the royal order of code scribes did thus feel a great advance.

But confusion still reigned on the face of the deep, and null was returned on every call to get() . . .

More Peopleware quotes

“The business we’re in is more sociological than technological, more dependent on workers’ abilities to communicate with each other than their abilities to communicate with machines.”
― Tom DeMarco, Peopleware: Productive Projects and Teams

“The purpose of a team is not goal attainment but goal alignment.”
― Tom DeMarco, Peopleware : Productive Projects and Teams

“The statistics about reading are particularly discouraging: The average software developer, for example, doesn’t own a single book on the subject of his or her work, and hasn’t ever read one.”
― Tom DeMarco, Peopleware: Productive Projects and Teams

“On the best teams, different individuals provide occasional leadership, taking charge in areas where they have particular strengths. No one is the permanent leader, because that person would then cease to be a peer and the team interaction would begin to break down.”
― Tom DeMarco, Peopleware : Productive Projects and Teams

“Whether you call it a “team” or an “ensemble” or a “harmonious work group” is not what matters; what matters is helping all parties understand that the success of the individual is tied irrevocably to the success of the whole.”
― Tom DeMarco, Peopleware: Productive Projects and Teams

“So far, the results confirm the folklore: Programmers seem to be a bit more productive after they’ve done the estimate themselves, compared to cases in which the manager did it without even consulting them. When the two did the estimating together, the results tended to fall in between.”
― Tom DeMarco, Peopleware: Productive Projects and Teams