Wiki
Clone wikiXALPI / DesigningUnitTests
As stated before, being able to unit test an application's business logic was one the primary goals of developing XALPI, by introducing abstractions that allow you to replace those abstractions during runtime with fakes / mocks / stubs / whatever.
So the first thing you should know is that for each XEOModel in your application an interface with the name of the model was generated, and two implementations were also generated
- One implementation that uses boObject instances to support the abstraction (production code)
- One implementation that uses JSON Objects to support the abstraction.
So if the XEOModel is Car, you have the following:
#!java public interface Car extends BaseModel; public class CarImpl implements Car; public class CarJSON implements Car;
Let's imagine you have the following method in a class
#!java public class CarChecker(){ public boolean isCarMercedes(Car carToCheck){ if ("Mercedes".equals(carToCheck.getName()) return true; else return false; } }
We begin the unit test by creating a method using JUnit, something like the following:
#!java @Test public void car_is_mercedes(){ Car mercedes = ; //Create a Car that is a mercedes somehow CarChecker checker = new CarChecker(); boolean value = checker.isCarMercedes(mercedes); Assert.assertTrue("Car should be a mercedes",value); }
So the only question here is... how do I create a Car that does not require the XEO Application to be up? Simple, you use the JSON implementation like the following;
CarMercedes.txt
#!javascript { sys_classname : 'Car' , name : 'Mercedes' }
And then you make something like:
#!java private JSONObject loadCar(){ JSObject obj = readFileAsJSON("CarMercedes.txt"); } @Test public void car_is_mercedes(){ Car mercedes = new CarJSON(loadCar()); CarChecker checker = new CarChecker(); boolean value = checker.isCarMercedes(mercedes); Assert.assertTrue("Car should be a mercedes",value); }
Each XEOModel has a JSON Implementation with a constructor that accepts a JSON Object as parameter. The JSON Object has a required field which is the sys_classname (which should have the Model name as a string). It can also have the following "System" attributes (which will be familiar to anyone using the XEO API)
#!javascript { "sys_exists" : true/false //Whether or not the object is supposed to be persisted , "sys_changed" : true / false //Whether or not the object is supposed to be changed , "sys_disabled" : true / false // Whether or not the object is supposed to be disabled , "sys_dtcreate" : "10-06-2008 13:54:34" //Date of creation , "sys_dtupdate" : "12-04-2009 23:34:14" //Date of last update , "sys_classname" : "Car" //Type of XEO Model , "parent$" : { ANOTHER OBJECT DEFINITION } //The parent (if any) , "sys_obj_errors" : [ "Error1", "Error2", "Error3"] //Errors in the object , "sys_att_errors" : { "attributeName" : "attributeError", "attributeName2" : "attributeError2" } //Attribute errors , "BOUI" : 11111 //The BOUI of the instance }
For the remaining attributes you use the following (notice the brand and colors attributes (an object relation and collection relation, respectively:
#!javascript { "name" : "Mercedes" , "year" : 2009 , "type" : "0" , "brand" : { "sys_classname" : "ModelBrand" , "name" : "Mercedes"} , "colors" : [ {"sys_classname" : "Color" , "name" : "Red" }, {"sys_classname" : "Color" , "name" : "Blue" }] }
Creating and Loading Objects with the JSON Implementation
If you want to create/load instances, you use the MODEL_NAMEManager class. If you want to "fake" that creation/loading process you need to do the following: In each Manager class there's this method available:
#!java public static void setFactory(PatientFactory newFactory);
You can use that to change the existing Factory, for instance, you switch the default CarFactory with a JSON Implementation, like this (example is a JUnit test):
#!java @Test public void test_create_new_car(){ CarFactoryJSON factory = new CarFactoryJSON(); factory.addModel(createNewPatientFromJSONFile()); //Instance has BOUI = 300 CarManager.setFactory(factory); Car loaded = CarManager.load(300); Assert.assertTrue(loaded.getName().equals("Test")); }
Each JSONFactory has several methods to allow you to manipulate the process, and you can also implement your own factories :)
Listing with the JSON Implementation
In case you want to unit test methods that require access to the list API, you can setup a "database" for the lists like the following:
#!javascript [ { "description" : "Empty CarList" , "modelName" : "Car" , "where" : "name = ?" , "args" : { "name" : "Mercedes" } ,"data" : [] } ,{ "description" : "Car with one element" , "modelName" : "Car" , "where" : "name = ? and date = ?" , "args" : { "name" : "BMW" , "date" : "23-07-2013 00:00:00" } ,"data" : [ { "sys_classname" : "Car" , "dateOpen" : "23-07-2013 00:00:00" , "name" : "BMW" , "file" : { "sys_classname" : "ModelBrand", "name" : "BMW" } } ] } ]
#!java String database = readFileFromString("CarDatabase.txt"); SelectFactory selectFactory = new SelectFactoryJSON(new JSONArray(database)); Select.setFactory( selectFactory );
#!java Select.Car().build();
#!java Select.Car().where("name = ?").args("Mercedes").build();
Warning about Listing Databases
It's not prudent to have a BIG database with all the listings you want to execute in tests (like a "test database"), it's better to have small "databases" with just the data for your specific test. If not it will quickly become a mess, believe me :)
Checking instances that were created
If you have a method that creates a given instance of a model and want to check if it's well created, you can do the following:
#!java public class MercedesCreator(){ //Method to test public void createCar(){ Car car = CarManager.create(); car.setName("Mercedes"); car.update(); } }
#!java @Test public void carCreated(){ CarFactoryJSON carFactory = new CarFactoryJSON(); CarManager.setFactory(patientFactory); MercedesCreator c = new MercedesCreator(); c.createCar(); List<Car> created = carFactory.getCreatedInstances(); if (created.isEmpty()){ Assert.fail("Should have at least one"); } else { Car createdCar = created.get(0); Assert.assertTrue(createdCar.getName().equals("Mercedes")); } }
Summary of generated classes for a given XEO Model (Car.xeomodel as example)
#!java //Model public interface Car extends BaseModel; //Interface that represents the model public class CarImpl implements Car; //Class that uses a boObject to support the abstraction public class CarJSON implements Car; //Class used for unit testing Car //Collection of Model public interface CarCollection extends Iterator<Patient>,Iterable<Patient>; //Interface that represents a Bridge/Collection of Car public class CarCollectionImpl implements CarCollection; //Class that uses a bridgeHandle to support the collection public class CarCollectioNJSON implements CarCollection; //Class for unit testing Car bridges/collection //Model Loading/Creating public interface CarFactory; public class CarFactoryImpl implements CarFactory; //Creates and Loads boObject instances as normally public class CarFactoryJSON implements CarFactory; //Create and Load JSON instances for unit testing public class CarManager; //Factory class that uses the factory implementation to load/create instances //Listing of instances public interface CarList extends Iterable<Car>; //Interface to represent a list of instances public class CarListImpl implements CarList; //Class that uses a boObjectList to support the list public class CarListJSON implements CarList; //Class that uses a List<Car> or a JSONArray to support the list (for unit testing) //Creating Lists of instances (through the Select class) public interface CarListBuilder; //Interface to allow the use of the builder pattern to create a new CarList public class CarListBuilderImpl implements CarListBuilder; //Implementation using boObjectLists public class CarListBuilderJSON implements CarListBuilder; //Implementation using JSON Arrays (for unit testing)
Updated