Monday, June 29, 2015

Unexpected MboSet Reset

I spent about a half day on this one.

We have a customization where the MEASUREMENT table contains a TICKETID column pointing to a record in the TICKET table.

There is a relationship called TICKETS with these properties:
WHERECLAUSE: ticketid = :ticketid

 MboRemoteSet measurements = /* an mboset of measurements. */  
 MboRemote measurement = /* a measurement record being updated */  
 MboSetRemote tickets = measurement.getMboSet("TICKETS");  
 MboRemote ticket = tickets.add();  
 measurement.setValue("TICKETID", ticket.getString("TICKETID"));  
 /* in a later block of code, but in the same transaction */  
 MboSetRemote tickets = measurement.getMboSet("TICKETS");  
 MboRemote ticket = tickets.getMbo(0);  
 /* ticket is now null! */  

The problem was a reset() on the second getMboSet("TICKETS").

The first getMboSet("TICKETS") created a related MboSet to the TICKETS table.  Internally, Maximo stores the relationship where clause used to create that relationship.  In the first case, that relationship is "ticketid = ''".

In the second getMboSet("TICKETS") the relationship where clause has become "ticketid = 'new ticketid'".  Maximo sees this difference and resets the MboSet before returning it to the caller.

I ended up with a MEASUREMENT record referencing a TICKET that didn't exist in the database. 

The fix was to reset the relationship after setting TICKETID.

 measurement.setValue("TICKETID", ticket.getString("TICKETID"));  
 RelationInfo ri = measurements.getMboSetInfo().getRelationInfo(tickets.getRelationName()); 
 SqlFormat sf = new SqlFormat(measurement, ri.getSqlExpr()); 

This reset the relationship where clause between MEASUREMENT and TICKETS without resetting the MboSet and losing my new TICKET object.

Tuesday, June 23, 2015

MXJUnit: Introduction

MXJUnit is a unit testing framework for Maximo by Interloc Solutions built on top of JUnit.  It permits testing of classes that normally run in the MXServer --- not UI classes like DataBeans --- within Eclipse itself.  This includes MBOs and field validators, but also crons and the MIF.

MXJUnit provides the following features:
  • capture e-mails from being sent during tests, but also permits writing tests to confirm that an e-mail has been sent and verify its contents.
  • capture logger output.
  • generate work orders.
  • run crons from a test, optionally using a subclass specific to the test.  This allows changing behaviour for the test, usually to have the cron work on a known subset of data.
  • capture output from PublishChannels.
  • send data to an Enterprise Service to test UserExits.
  • send data to an Object Structure to test processing classes.
  • replace Maximo property (MAXPROP) values, system properties and MAXVARS temporarily for the duration of a test.
  • replace an MBO class for the duration of a test.
  • automatically delete MBOs created during the test after the test has completed.

Writing test cases using MXJUnit should follow normal best practices for writing regular JUnit tests.

MXJUnit tests should extend from MaximoTestHarness or one of its subclasses.  Special subclasses exist for testing Crons and for testing MIF.  MaximoTestHarness provides methods for interacting with Maximo.  Some of the more useful methods are:
  • generateWorkOrder:  generates work orders given a PM object and lead time information.
  • getLogCapture: get the logger output generated during the test.
  • getMboSet: get an MboSet, optionally specifying a user and app to enable application security.
  • refresh: given an MBO, reload a fresh copy from the database.
  • refreshUserSecurity: after making ApplicationAuth changes, reloads security settings for a given user.
  • spyOnMXServer: return an MXServer instance that has been mocked using Mockito.

Here is a sample unit test that retrieves a work order by wonum and verifies that the record returned was the record expected.

 package com.interlocsolutions.demo;   
 import java.rmi.RemoteException;   
 import static org.junit.Assert.*;   
 import org.junit.Test;   
 import psdi.mbo.MboRemote;   
 import psdi.mbo.MboSetRemote;   
 import psdi.util.MXException;   
 import com.interlocsolutions.maximo.junit.MaximoTestHarness;   
 public class MXDemoTest extends MaximoTestHarness {     
   public void testWorkOrder() throws MXException, RemoteException {       
    // Setup       
    MboSetRemote msr = getMboSet("WORKORDER");       
    msr.setWhere("wonum = '1022'");       
    MboRemote mr = msr.getMbo(0);       
    // Execute       
    String wonum = mr.getString("WONUM");           
    // Verify       
    assertEquals("1022", wonum);     

In the Setup phase, a WORKORDER MboSet is created and the work order with wonum='1022' is retrieved.

In the Execute phase, the WONUM field is retrieved.

In the Verify phase, the WONUM is confirmed to be 1022.  Every test should assert something or the test doesn't test anything.

Here is a sample walk through creating and running an MXJUnit test.


I've been working with Maximo for over 10 years.  I started with version 5.

What I'm hoping to do is provide some deep technical information on Maximo.  These would be things I've discovered over time and usually out of necessity.