Thursday, April 20, 2017

Instantiating an object in Java without calling a constructor

The Scenario


This isn't a cliche...


I was working on a Java project that talks to a web service with a custom "Client" class that was written internally.  In my project, the client is pulled in from a hosted maven repo as a dependency, so I didn't have access to the source (at least not from this project).  I'm trying to be better about writing characterization tests for existing code before I go in and change anything (if nothing else it's a good way to feel out the code). But I had a problem...

The Client has a single constructor, which takes an endpoint, a username, and a password. Calling this constructor tries to connect to the endpoint (and crashes if it fails).  Because this was the only defined constructor, subclassing wasn't going to help me.  It doesn't implement an interface, so that avenue was also closed down.

So there was no obvious way for me to create a test double to pass in to the code I was actually interested in testing.  How in the hell was I going to get around this???


Magic... errr Reflection and Deserialization


After some fruitless dicking around with who knows what (I wrote this code a month ago... should have wrote it up then lol), I turned to my failsafe programming tool: Google.  We eventually found this article by Dr. Heinz M. Kabutz creatively titled "Creating Objects Without Calling Constructors".  Sweet!

His approach was to use object deserialization to create an object without ever calling any of it's constructors.  This was perfect for me: I wanted a subclass (so it could be substitutable for the real class) that never called the parent constructor (because this isn't an integration test).  I still needed to define a constructor that called the superclass constructor (otherwise the compiler cries), but I could make this a private constructor and ignore it.  In it's place, I have a static getInstance() method that calls the magical SilentObjectCreator class described in the article.



Creating objects from nothing


I'm not an expert in Java reflection.  I'd used a little bit in a factory method once, but it just wasn't a tool I've turned to often.  The SilentObjectCreator class uses sun.reflect.ReflectionFactory to create a serialization constructor.  It's not entirely accurate to say we never call any constructor.  We can avoid calling the class and superclass constructors, but the Object constructor is still being called.  But that I can live with.  Here is the code that makes the magic happen:

public class SilentObjectCreator {
  public static <T> T create(Class<T> clazz) {
    return create(clazz, Object.class);
  }

  @SuppressWarnings("rawtypes")
  public static <T> T create(Class<T> clazz, Class<? super T> parent) {
    try {
     
      ReflectionFactory rf = ReflectionFactory.getReflectionFactory();
      Constructor objDef = parent.getDeclaredConstructor();
      Constructor intConstr = rf.newConstructorForSerialization(clazz, objDef);
      return clazz.cast(intConstr.newInstance());
      
    } catch (RuntimeException e) {
      throw e;
      
    } catch (Exception e) {
      throw new IllegalStateException("Cannot create object", e);
    }
  }
}


Seriously, that's it.  It's super simple yet it gets the job done.  The test client looks something like this:


public class FakeClient extends Client{

    private Response response;

    public static FakeClient getInstance(){
        return SilentObjectCreator.create(FakeClient.class);
    }
    
    //never ever ever call this... ever
    private FakeClient(String endpoint, String user, String password) throws 
                                        RemoteException, MalformedURLException {
        super(endpoint, user, password);
    }

    //get and set the response we want the fake client to return
    @Override
    public Response processRequest(Request data) throws 
                                        RemoteException {
        return response;
    }
    
    public void setResponse(Response response){
        this.response = response;
    }
}


This lets me write tests like this:


    @Test
    public void ProcessRequest_ResponseHasErrors_LogsException(){ 

        //Arrange
        FakeResponse fakeResponse = getTestResponse();
        fakeResponse.setErrorMessage("TEST RESPONSE ERROR MESSAGE");
        Response response = fakeResponse;
        
        FakeClient fakeClient = FakeClient.getInstance();
        fakeClient.setResponse(response);
        Client client = fakeClient;
        
        ClientFactory factory = createFakeFactory(client);
        WrapperAPI sut = new WrapperAPIImpl(factory);
        Authentication auth = getTestAuth();
        
        //Act
        try {
            sut.processRequest(getTestRequest(), auth);
        } catch (ProcessingException e) {}
        
        //Assert
        assertTrue(out.toString().contains("TEST RESPONSE ERROR MESSAGE"));
    }


I'm actually testing the WrapperAPIImpl, which takes a "factory".  This factory is what actually provides the client (this was something I added to make testing possible... before the code was just newing up a client O_o).  I create the FakeClient, assign it to a Client object, and pass it to the code, which is none the wiser.  I described the log capturing in a previous post, I just need to make sure the error message is logged.  Shazzam, green test!

Granted, there are plenty of reasons to hate this test.  Way too much setup, too much knowledge of inner workings make it brittle... blah blah blah.  At least I have some test, which will allow me to refactor the code (and maybe address some of the shortcomings this test exposes).

The fact that this works is a little bit like magic...







This gif was perfect but distracting, so it's parked down here



No comments:

Post a Comment