Entity Tests
Now that we have some confidence that our data decorators are working as expected, let’s move up a level and test our data entities. The Client class is the root of our model hierarchy and by testing that, we can test our other models in the process.
We already have client-tests.cpp in cm-tests/source/models that Qt Creator added for us when we created the project, so go ahead and add a companion header file client-tests.h.
client-tests.h:
#ifndef CLIENTTESTS_H #define CLIENTTESTS_H
#include <QtTest> #include <QJsonObject>
#include <models/client.h> #include <test-suite.h>
namespace cm { namespace models {
class ClientTests : public TestSuite { Q_OBJECT
public: ClientTests();
private slots: void constructor_givenParent_setsParentAndDefaultProperties(); void constructor_givenParentAndJsonObject_setsParentAndProperties(); void toJson_withDefaultProperties_constructsJson(); void toJson_withSetProperties_constructsJson(); void update_givenJsonObject_updatesProperties(); void update_givenEmptyJsonObject_updatesPropertiesToDefaults();
private: void verifyBillingAddress(const QJsonObject& jsonObject); void verifyDefaultBillingAddress(const QJsonObject& jsonObject); void verifyBillingAddress(Address* address); void verifyDefaultBillingAddress(Address* address); void verifySupplyAddress(const QJsonObject& jsonObject); void verifyDefaultSupplyAddress(const QJsonObject& jsonObject); void verifySupplyAddress(Address* address); void verifyDefaultSupplyAddress(Address* address); void verifyAppointments(const QJsonObject& jsonObject); void verifyDefaultAppointments(const QJsonObject& jsonObject); void verifyAppointments(const QList<Appointment*>& appointments); void verifyDefaultAppointments(const QList<Appointment*>& appointments); void verifyContacts(const QJsonObject& jsonObject); void verifyDefaultContacts(const QJsonObject& jsonObject); void verifyContacts(const QList<Contact*>& contacts); void verifyDefaultContacts(const QList<Contact*>& contacts);
QByteArray jsonByteArray = R"( { "reference": "CM0001", "name": "Mr Test Testerson", "billingAddress": { "building": "Billing Building", "city": "Billing City", "postcode": "Billing Postcode", "street": "Billing Street" }, "appointments": [ {"startAt": "2017-08-20T12:45:00", "endAt": "2017-08-
20T13:00:00", "notes": "Test appointment 1"}, {"startAt": "2017-08-21T10:30:00", "endAt": "2017-08-
21T11:30:00", "notes": "Test appointment 2"} ], "contacts": [ {"contactType": 2, "address":"email@test.com"}, {"contactType": 1, "address":"012345678"} ], "supplyAddress": { "building": "Supply Building", "city": "Supply City", "postcode": "Supply Postcode", "street": "Supply Street" } })"; };
}}
#endif
There are three main areas we want to test here:
- Object construction
- Serialization to JSON
- Deserialization from JSON
As with previous suites, we have a couple of different flavors of test for each area—one with default data and one with specified data. In the private section, you will see numerous verify methods. They are to encapsulate the functionality required to test a particular subset of our data. The advantages of doing this are the same as with regular code: they make the unit tests much more concise and readable, and they allow easy reuse of the validation rules. Also, in the private section, we define a blob of JSON we can use to construct our Client instances. A QByteArray, as its name suggests, is simply an array of bytes that comes with numerous associated helpful functions:
void ClientTests::constructor_givenParent_setsParentAndDefaultProperties() { Client testClient(this); QCOMPARE(testClient.parent(), this); QCOMPARE(testClient.reference->value(), QString("")); QCOMPARE(testClient.name->value(), QString("")); verifyDefaultBillingAddress(testClient.billingAddress); verifyDefaultSupplyAddress(testClient.supplyAddress); verifyDefaultAppointments(testClient.appointments-
>derivedEntities()); verifyDefaultContacts(testClient.contacts->derivedEntities()); }
void ClientTests::constructor_givenParentAndJsonObject_setsParentAndProperties() { Client testClient(this, QJsonDocument::fromJson(jsonByteArray).object()); QCOMPARE(testClient.parent(), this); QCOMPARE(testClient.reference->value(), QString("CM0001")); QCOMPARE(testClient.name->value(), QString("Mr Test Testerson")); verifyBillingAddress(testClient.billingAddress); verifySupplyAddress(testClient.supplyAddress); verifyAppointments(testClient.appointments->derivedEntities()); verifyContacts(testClient.contacts->derivedEntities()); }
Starting with the constructor tests, we instantiate a new Client, both with and without a JSON object. Note that in order to convert our JSON byte array to a QJsonObject, we need to pass it through a QJsonDocument. Once we have our initialized client, we check the name property and utilize the verify methods to test the state of the child objects for us. Regardless of whether or not we supply any initial data via a JSON object, we expect the supplyAddress and billingAddress objects to be created for us automatically as well as the appointments and contacts collections. By default, the collections should be empty:
void ClientTests::toJson_withDefaultProperties_constructsJson() { Client testClient(this); QJsonDocument jsonDoc(testClient.toJson()); QVERIFY(jsonDoc.isObject()); QJsonObject jsonObject = jsonDoc.object(); QVERIFY(jsonObject.contains("reference")); QCOMPARE(jsonObject.value("reference").toString(), QString("")); QVERIFY(jsonObject.contains("name")); QCOMPARE(jsonObject.value("name").toString(), QString("")); verifyDefaultBillingAddress(jsonObject); verifyDefaultSupplyAddress(jsonObject); verifyDefaultAppointments(jsonObject); verifyDefaultContacts(jsonObject); }
void ClientTests::toJson_withSetProperties_constructsJson() { Client testClient(this, QJsonDocument::fromJson(jsonByteArray).object()); QCOMPARE(testClient.reference->value(), QString("CM0001")); QCOMPARE(testClient.name->value(), QString("Mr Test Testerson")); verifyBillingAddress(testClient.billingAddress); verifySupplyAddress(testClient.supplyAddress); verifyAppointments(testClient.appointments->derivedEntities()); verifyContacts(testClient.contacts->derivedEntities()); QJsonDocument jsonDoc(testClient.toJson()); QVERIFY(jsonDoc.isObject()); QJsonObject jsonObject = jsonDoc.object(); QVERIFY(jsonObject.contains("reference")); QCOMPARE(jsonObject.value("reference").toString(), QString("CM0001")); QVERIFY(jsonObject.contains("name")); QCOMPARE(jsonObject.value("name").toString(), QString("Mr Test
Testerson")); verifyBillingAddress(jsonObject); verifySupplyAddress(jsonObject); verifyAppointments(jsonObject); verifyContacts(jsonObject); }
The toJson() tests follow much the same pattern. We construct an object without a JSON object so that we get default values for all the properties and child objects. We then immediately construct a QJsonDocument using a call to toJson() in the constructor to get the serialized JSON object for us. The name property is tested, and then we utilize the verify methods once more. When constructing a Client using JSON, we add precondition checks to ensure that our properties have been set correctly before we again call toJson() and test the results:
void ClientTests::update_givenJsonObject_updatesProperties() { Client testClient(this); testClient.update(QJsonDocument::fromJson(jsonByteArray).object()); QCOMPARE(testClient.reference->value(), QString("CM0001")); QCOMPARE(testClient.name->value(), QString("Mr Test Testerson")); verifyBillingAddress(testClient.billingAddress); verifySupplyAddress(testClient.supplyAddress); verifyAppointments(testClient.appointments->derivedEntities()); verifyContacts(testClient.contacts->derivedEntities()); }
void ClientTests::update_givenEmptyJsonObject_updatesPropertiesToDefaults() { Client testClient(this, QJsonDocument::fromJson(jsonByteArray).object()); QCOMPARE(testClient.reference->value(), QString("CM0001")); QCOMPARE(testClient.name->value(), QString("Mr Test Testerson")); verifyBillingAddress(testClient.billingAddress); verifySupplyAddress(testClient.supplyAddress); verifyAppointments(testClient.appointments->derivedEntities()); verifyContacts(testClient.contacts->derivedEntities()); testClient.update(QJsonObject()); QCOMPARE(testClient.reference->value(), QString("")); QCOMPARE(testClient.name->value(), QString("")); verifyDefaultBillingAddress(testClient.billingAddress); verifyDefaultSupplyAddress(testClient.supplyAddress); verifyDefaultAppointments(testClient.appointments-
>derivedEntities()); verifyDefaultContacts(testClient.contacts->derivedEntities()); }
The update() tests are the same as toJson(), but the other way around. This time, we construct a JSON object using our byte array and pass it in to update(), checking the state of the model afterward.
The various private verification methods are all simply sets of checks that save us having to repeat the same code over and over. Consider the given example:
void ClientTests::verifyDefaultSupplyAddress(Address* address) { QVERIFY(address != nullptr); QCOMPARE(address->building->value(), QString("")); QCOMPARE(address->street->value(), QString("")); QCOMPARE(address->city->value(), QString("")); QCOMPARE(address->postcode->value(), QString("")); }
Build and run the unit tests again and the new Client tests should all happily pass.