Testing the Travel Impressions model

After the model is designed, generated, and initialized with some data, the model should be tested to validate it further. In addition, writing tests by applying the techniques we learned in Chapter 3, Structuring Code with Classes and Libraries, is the best way to start learning dartling. Testing is done in the test/travel/impressions/travel_impressions_test.dart file. The main function creates a repository based on the JSON definition of the model and passes it to the testTravelData function:

testTravelData(TravelRepo travelRepo) {
  testTravelImpressions(travelRepo, TravelRepo.travelDomainCode,
      TravelRepo.travelImpressionsModelCode);
}

void main() {
  var travelRepo = new TravelRepo();
  testTravelData(travelRepo);
}

The testTravelImpressions function accepts the repository and the names of the domain and the model. In a group of tests, before each test, a setup is done to first obtain the three variables: models, session, and entries. The models (here, only one) are obtained from the repository based on the domain's name. A session, with a history of actions and transactions of dartling, is created by the newSession method of the models object. The entries variable for the only model is obtained from the model's object by using the model's name:

testTravelImpressions(Repo repo, String domainCode, String modelCode) {
  var models;
  var session;
  var entries;

  Countries countries;
  Country bosnia;
  Oid darivaOid, oid;
    Travelers travelers;
  group("Testing ${domainCode}.${modelCode}", () {
    setUp(() {
      models = repo.getDomainModels(domainCode);
      session = models.newSession();
      entries = models.getModelEntries(modelCode);
      expect(entries, isNotNull);

      countries = entries.countries;
      travelers = entries.travelers;
      initTravelImpressions(entries);

      var code = 'BA';
      bosnia = countries.singleWhereCode(code);
      darivaOid = bosnia.places.firstWhereAttribute('name',  'Dariva').oid;
    });
    tearDown(() {
      entries.clear();
    });
    test("Not empty entries test", () {
      expect(!entries.isEmpty, isTrue);
    });
    // code left out

The countries and travelers are the entry entities. The initTravelImpressions function is called to initiate the model with some basic data. After the initialization, the country Bosnia is found in the countries object based on its unique code. A single place, with the Dariva name, is retrieved from the bosnia object and its oid is kept in the darivaOid variable. The oid attribute is inherited from dartling. It is a unique timestamp used as a system identifier in a collection of entities. Each test has an access to all these variables. After a test is run, the entries object is cleared so that the setUp function may start from the empty model. The first test is run to show that the entries object is not empty after the setup. A single country is found by the singleWhereCode method based on the inherited code attribute. In the Country concept, the code attribute is used. In the countries object, each country must have a unique code:

    test('Find country by code', () {
      var code = 'BA';
      Country country = countries.singleWhereCode(code);
      expect(country, isNotNull);
      expect(country.name, equals('Bosnia and Herzegovina'));
    });

A single entity may be found by its user identifier. In the Country concept, the user identifier is the name attribute:

    test('Find country by id', () {
      Id id = new Id(countries.concept);
      id.setAttribute('name', 'Bosnia and Herzegovina'),
      Country country = countries.singleWhereId(id);
      expect(country, isNotNull);
      expect(country.code, equals('BA'));
    });

If a concept has one attribute identifier (simple identifier), a creation of an ID object may be avoided by using a shortcut method called singleWhereAttributeId:

    test('Find country by name attribute id', () {
      var name = 'Bosnia and Herzegovina';
      Country country = countries.singleWhereAttributeId('name', name);
      expect(country, isNotNull);
      expect(country.code, equals('BA'));
    });

The first entity with an attribute value equal to a value given to the firstWhereAttribute method will then be obtained. If this attribute is an identifier, methods that use identifiers will perform faster:

    test('Find country by name attribute', () {
      var name = 'Bosnia and Herzegovina';
      Country country = countries.firstWhereAttribute('name', name);
      expect(country, isNotNull);
      expect(country.code, equals('BA'));
 });

If an entity is not a member of a collection of entities, a search method will return null:

  test('Find country by name attribute id', () {
      var name = 'Poland';
      Country country = countries.singleWhereAttributeId('name', name);
      expect(country, isNull);
  });

The Place concept has a composite identifier, composed of the country neighbor and the name attribute. If only the name attribute is used, singleWhereAttributeId will return null:

    test('Find country and (not) place by name id', () {
      var countryName = 'Bosnia and Herzegovina';
      Country country = countries.singleWhereAttributeId('name', countryName);
      var placeName = 'Dariva';
      Places places = country.places;
      Place place = places.singleWhereAttributeId('name', placeName);
      expect(place, isNull);
    });

The name attribute may be used to find both a country and its place:

    test('Find country and place by name attribute', () {
      var countryName = 'Bosnia and Herzegovina';
      bosnia = countries.firstWhereAttribute('name', countryName);
      expect(bosnia, isNotNull);
      var placeName = 'Dariva';
      Places places = bosnia.places;
      Place place = places.firstWhereAttribute('name', placeName);
      expect(place, isNotNull);
      expect(place.city, equals('Sarajevo'));
    });

However, the use of identifiers is recommended for performance reasons:

    test('Find country and place by id', () {
      var countryName = 'Bosnia and Herzegovina';
      bosnia = countries.singleWhereAttributeId('name', countryName);
      var placeName = 'Dariva';
      Places places = bosnia.places;
      Id id = new Id(bosnia.concept);
      id.setParent('country', bosnia);
      id.setAttribute('name', placeName);
      Place place = places.singleWhereId(id);
      expect(place, isNotNull);
      expect(place.city, equals('Sarajevo'));
    });

The same results may be obtained by using more elegant method cascades. Note that the bosnia variable is used in order to avoid searching for the country. Besides, in the last line, the place's oid attribute is assigned to the oid variable that will be used in the next test:

    test('Find country and place by id (method cascades)', () {
      var placeName = 'Dariva';
      Places places = bosnia.places;
      Id id = new Id(bosnia.concept)
        ..setParent('country', bosnia)
        ..setAttribute('name', placeName);
      Place place = places.singleWhereId(id);
      expect(place, isNotNull);
      expect(place.city, equals('Sarajevo'));
      oid = place.oid;
    });

In the model, the Country concept is an entry. The relationship between the Country and Place concepts is internal. A place may be searched by its oid attribute starting with the countries' entry, followed by the internal neighbors from the Country concept:

    test('Find place by oid by searching from countries down', {
      Place place = countries.singleDownWhereOid(darivaOid);
      expect(place, isNotNull);
      expect(place.name, equals('Dariva'));
    });

A specific method that does a job of finding an entity based on an identifier value may be used:

    test('Find place by a specific method', () {
      var placeName = 'Dariva';
      Place place = bosnia.places.findById(placeName, bosnia);
      expect(place, isNotNull);
      expect(place.city, equals('Sarajevo'));
    });

The findById method is added to the Places class in the lib/travel/impressions/places.dart file:

  Place findById(String name, Country country) {
    return singleWhereId(new Id(concept)..setAttribute('name', name)..
        setParent('country', country));
  }

The city attribute of the Place concept is not required (not in bold in the Travel Impression model figure). Thus, it is not an identifier or a part of an identifier (not in italics). All the places in the city of Sarajevo may be selected by the selectWhereAttribute method. The select methods return a new collection of entities:

  test('Select places in Sarajevo', () {
      Places places = bosnia.places.selectWhereAttribute('city', 'Sarajevo'),
      expect(places.length, greaterThan(0));
        for (var place in places) {
          expect(place.city, equals('Sarajevo'));
          }
    });

A specific read-only property (with only the get method) may be used in an anonymous function of the selectWhere method to select a subset of entities:

    test('Select places by function', () {
      Places places = bosnia.places.selectWhere((place) => place.old);
      expect(places.length, greaterThan(0));
      for (var place in places) {
        expect(place.description, contains('old')));
       }
    });

The old property with the get method is added to the Places class in the lib/travel/impressions/places.dart file:

  bool get old => description.contains('old') ? true : false;

The places of the bosnia object are sorted by the city attribute:

    test('Sort places by city in Bosnia and Herzegovina', () {
      bosnia.places.sort(
          (place1, place2) => place1.city.compareTo(place2.city));
    });

A new place is not added because the required values for the name attribute and the country neighbor are missing. The corresponding error messages are added to the errors property of the places object:

    test('Add place required error', () {
      Places places = bosnia.places;
      var placesCount = places.length;
      var place = new Place(places.concept);
      expect(place, isNotNull);

      var added = places.add(place);
      expect(added, isFalse);
      expect(places.length, equals(placesCount));
      places.errors.display(title:'Add place required error'),

      expect(places.errors.length, equals(2));
      expect(places.errors.toList()[0].category, equals('required'));
      expect(places.errors.toList()[0].message,
          equals('Place.name attribute is null.'));
      expect(places.errors.toList()[1].category, equals('required'));
      expect(places.errors.toList()[1].message,
          equals('Place.country parent is null.'));
    });

The error messages also appear in the console of the Dart Editor.

If we want to add a place that already exists according to its identifier, the add method will not be successful:

    test('Add place unique error', () {
      Places places = bosnia.places;
      var placesCount = places.length;
      var place = new Place(places.concept);
      expect(place, isNotNull);

      place.name = 'Dariva';
      place.country = bosnia;
      var added = places.add(place);
      expect(added, isFalse);
      expect(places.length, equals(placesCount));

      places.errors.display(title:'Add place unique error'),
      expect(places.errors.length, equals(1));
      expect(places.errors.toList()[0].category, equals('unique'));
    });

In dartling, there are pre and posthooks for the add and remove methods. A pre-add hook may be used to validate a specific constraint that is not defined in the model:

    test('Add place pre validation error', () {
      Places places = bosnia.places;
      var placesCount = places.length;
      var place = new Place(places.concept);
      expect(place, isNotNull);
      place.name =
      'A new place with a name longer than 32 cannot be accepted';
      place.country = bosnia;
      var added = places.add(place);
      expect(added, isFalse);
      expect(places.length, equals(placesCount));
      places.errors.display(title:'Add place pre validation error'),
      expect(places.errors.length, equals(1));
      expect(places.errors.toList()[0].category, equals('pre'));
    });

The specific preAdd method is defined in the Places class. The method is called by the add method of dartling:

  bool preAdd(Place place) {
    bool validation = super.preAdd(place);
    if (validation) {
      validation = place.name.length <= 32;
      if (!validation) {
        var error = new ValidationError('pre'),
        error.message =
            '${concept.codePlural}.preAdd rejects the "${place.name}" '
            'name that is longer than 32.';
        errors.add(error);
      }
    }
    return validation;
  }

Finally, a new place is added:

    test('Add place', () {
      Places places = bosnia.places;
      var placesCount = places.length;
      var place = new Place(places.concept);
      expect(place, isNotNull);

      place.name = 'Ilidza';
      place.city = 'Sarajevo';
      place.country = bosnia;
      var added = places.add(place);
      expect(added, isTrue);
      expect(places.length, equals(++placesCount));
    });

The Travel Impressions app is further worked out to show all the data that was input: the countries and their places, the impressions, and the web links associated with each place, or the travelers and their impressions. These screens are not application-specific, but use the views, menu bars, and so on in the included dartling_default_app library, showing the advantage of starting with a modeling framework.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.254.44