Accelerating time to test aggregation

To be able to better analyze the result of aggregation in your solution, you can speed up event submission. You can write a JUnit test to simulate a much longer time period than the time that the test runs.

Before you begin

Create a JUnit test project and import the java.time and java.util.concurrent.ScheduledExecutorService packages in addition to the packages required to use test driver and the other Decision Server Insights runtime JAR files. For more information, see Setting the class path for automated tests on solutions.

About this task

Using the ZonedDateTime class, you can either advance time with each sent event, or create a separate thread to advance time in the background while your test is running. For example, the following code uses a ZonedDateTime object to send a randomly generated purchase event each day over a period of 10 days.

private Random random = new Random();
private ZonedDateTime eventTime;

eventTime = ZonedDateTime.now();
...
for(int n=0; n < 10; n++) {
   eventTime = eventTime.plusDays(1);
   Purchase purchase = MyEventFactory.createEvent(Purchase.class);
   purchase.setTimeStamp(eventTime);
   purchase.setCustomer(MyEventFactory.createRelationship(Customer.class, "John"));
   OrderItem item = MyEventFactory.createConcept(OrderItem.class));
   item.setAmount(Math.random() * 50 + 10);
   item.SetSku("Product" + n);
   purchase.addTo_orderItems(item);
   testDriver.submitEvent(purchase);
}
testDriver.endTest(); // end the test

To accelerate time when each test in your JUnit project begins, add a scheduled executor service to your Java class.

Warning: If the server is not restarted after you accelerate a test, events might be processed out of order. For example, if an engine processes event1 in the future (due to the acceleration of time) and then a new test sends event2 with an earlier time stamp than event1, the events are processed out of order.
Important: Global entity aggregates are not affected by test techniques that advance time. For example, if you set the TestEpoch property to a year ago, and then send an event from a year ago and another event time stamped today, the jobs that are scheduled to occur during this year are not run. The same is true if a global entity aggregate is queried from a rule or Java agent that use the keyword now; the value of now is the actual time, and not the time adjusted by the TestEpoch property.

Procedure

  1. Add fields for your time-related variables, test driver, and the executor service.
    private static final int MINUTES_PER_SEC = 30;
    private TestDriver driver;
    private Random random = new Random();
    private ZonedDateTime eventTime;
    private ZonedDateTime firstEventTime;
    private ScheduledExecutorService execSvc = Executors.newScheduledThreadPool(1);
  2. Create @Before methods to set up the test driver and the executor service.
    @Before
    public void initTestDriver() {
       try {
          driver = new TestDriver();
          driver.connect();
       } 
       catch (Exception exc) {
          exc.printStackTrace();
       }
    }
    
    @Before
    public void startUpdatingTime() {
       eventTime = ZonedDateTime.now();
       final ZonedDateTime start = eventTime;
       execSvc.schedule(new Runnable() {
          long elapsedMinutes = 0;
          long elapsedDays = 0;
          
          @Override
          public void run() {
             // move 1 hour every 2 seconds
             // 24 hours takes 48 seconds
             eventTime = eventTime.plusMinutes(MINUTES_PER_SEC);
             elapsedMinutes += MINUTES_PER_SEC;
             double d = (double) elapsedMinutes/60;
             if (d == 24) {
                elapsedDays++;
                System.out.println("######################################");
                System.out.println("# Event Time: "+eventTime);
                System.out.println("# Start time: "+start);
                System.out.println("# First event: "+firstEventTime);
                System.out.println("# Elapsed days (since test started): "+elapsedDays);
                System.out.println("# Elapsed mins (since test started): "+elapsedMinutes);
                System.out.println("######################################");
             }
             execSvc.schedule(this, 1, TimeUnit.SECONDS);
          }
       }, 1, TimeUnit.SECONDS);
    }
  3. Create an @After method to shut down the service.
    @After
    public void stopUpdatingTime() {
       execSvc.shutdown();
    }
  4. Create @Test methods to set the time of the first event and submit new events.

    For example, the following test sends randomly generated transaction events that affect a global event aggregate.

    @Test
    public void testAvgTransactionLastPeriodExtended() throws Exception {
       int iterations = 10;
       int entityCount = 100;
       int txCount = 0;
       long start = System.currentTimeMillis();
       for (int i=0;i<iterations;i++) {
          for (int j=0;j<entityCount;j++) {
             // Submit transactions with random amounts up to 50,000
             double amount = getRandomAmount();
             submitTransaction("account_"+j, "customer_"+j+"@fmail.com", amount);
             txCount++;
             Thread.sleep(200);
          }
       }
       long duration = System.currentTimeMillis() - start;
       System.out.println(String.format("Submitted [%s] events in [%s] ms", txCount, duration));
    }
    	
    private double getRandomAmount() {
       double val = 50000 * random.nextDouble();
       BigDecimal bd = new BigDecimal(val);
       bd = bd.setScale(2, RoundingMode.HALF_UP);
       return bd.doubleValue();
    }
    
    private void submitTransaction(String accountId, String customerId, double amount) throws Exception {
       submitTransaction(accountId, customerId, amount, null);
    }
    
    private void submitTransaction(String accountId, String customerId, double amount, ZonedDateTime time) throws Exception {
       Transaction transaction = createTransaction(accountId, customerId, amount, time);
       if (null == firstEventTime) {
          firstEventTime = transaction.get$Timestamp();
       }
       RoutingStatus status = driver.getSolutionGateway().submit(transaction);
       Assert.assertEquals(RoutingStatus.STATUS_OK, status);
    
    private Transaction createTransaction(String accountId, String customerId, double amount, ZonedDateTime time) throws Exception {
       ConceptFactory factory = driver.getConceptFactory(ConceptFactory.class);
       if (null == time) {
          time = this.eventTime;
       }
       Transaction transaction = factory.createTransaction(time);
       transaction.setCountryCode("US");
       transaction.setAmount(amount);
       Relationship<Account> account = driver.getEventFactory().createRelationship(Account.class, accountId);
       transaction.setAccount(account);
       Relationship<Customer> customer = driver.getEventFactory().createRelationship(Customer.class, customerId);
       transaction.setCustomer(customer);
       return transaction;
    }
    

Results

After you run your tests, you can retrieve information about the solution by making REST API calls. For more information, see Getting entity and aggregate values by using the REST API.

What to do next

You might want to capture a global event aggregate value during a test and emit a new event with this value. For more information, see Receiving and storing debug information.