Wednesday, October 23, 2013

Sanity Checking Angular.dart Application Code


I have played a little fast and loose with my Angular.dart code. Before moving on, I really need to sanity check my progress.

Sanity checks come in three main forms for me when working with new code. First, and probably most important, is better naming. As I better understand my domain and tools, I refine the names of classes and variables. Next, I try to reproduce some of my work so that I can have some confidence that I am not programming by coincidence. Lastly, I try to refactor some code to validate that my code is maintainable—that tests continue to pass and smoke tests don't hint at fire.

I start by renaming my AppointmentCtrl class as AppointmentController. The “Ctrl” abbreviation for controller is an AngularJS convention. I prefer full names for readability. It is a minor thing, but I keep finding my fingers typing the whole word, so I give in here. More importantly, I rename the ServerController class as AppointmentBackend. It is not a controller, which would be associated with an ng-controller directive. It is an HTTP backend wrapper.

With those changes, my module injection is now:
  var module = new AngularModule()
    ..type(AppointmentBackend)
    ..type(AppointmentController);

  bootstrapAngular([module]);
(bootstrapAngular() will be renamed ngBootstrap() any day now)

Next up: verify that I am not programming by coincidence. I think that I have a handle on how to build Angular.dart controllers and test them, let's find out!

After various testing, I have need of a remove action for my appointment controller:



In the HTML, I will want to use a remove() method on the controller to implement:
    <div appt-controller>
      <ul class="unstyled">
        <li ng-repeat="appt in day.appointments">
          <span>{{appt.time}} {{appt.title}}</span>
          <a ng-click="day.remove(appt)"><!-- ... --></a>
        </li>
      </ul>
      <!-- ... -->
    </div>
To drive the remove functionality into existence, I write a test. My test will verify that the server will be told to remove the record:
    // This setup already exists
    var server;
    setUp((){
      server = new AppointmentBackendMock();
    });
    test('removing a record removes it from the server', (){
      var controller = new AppointmentController(server);
      controller.remove({'id': '42'});

      server.
        getLogs(callsTo('remove', '42')).
        verify(happenedOnce);
    });
That fails since there is no remove() method in the AppointmentController class:
ERROR: Appointment controller removing a record removes it from the server
  Test failed: Caught Class 'AppointmentController' has no instance method 'remove'.
  
  NoSuchMethodError : method not found: 'remove'
  Receiver: Instance of 'AppointmentController'
  Arguments: ["42"]
  dart:core-patch/object_patch.dart                                                                                               Object.noSuchMethod
  ../test.dart 46:24     
I make the test pass with:
class AppointmentController {
  // ...
  void remove(String id) {
    _server.remove(id);
  }
  // ...
}
Next, I do the same for the AppointmentBackend. The test, using the Http setup from last night:
  group('Appointment Backend', (){
    var server, http_backend;
    setUp((){
      http_backend = new MockHttpBackend();
      var http = new Http(/* ... */);
      server = new AppointmentBackend(http);
    });
    test('remove will DELETE record', (){
      http_backend.
        expectDELETE('/appointments/42').
        respond('{}');

      server.remove('42');
    });
  });
And the code that makes that test pass:
class AppointmentBackend {
  Http _http;
  AppointmentBackend(this._http);
  remove(String id) {
    _http(method: 'DELETE', url: '/appointments/${id}');
  }
}
And, with the ng-click directive invoking the controller's remove() method, and the controller's remove() method invoking the backend's remove() method, which DELETEs the record from the backend, I am able to remove those old records:



That is actually fairly encouraging. I have been burned before when I tried to add a second test of the same class or object (due to unintended class coupling). With 5 passing tests and the ability to create an remove records, I think I will call it a night here and will pick back up with refactoring tomorrow.



Day #913

No comments:

Post a Comment