Code with sideeffects is hard to test, hard to reason about. It is of course necessary to have sideeffects because otherwise all that happens is that our computer running a program generates heat. We do have to manage them though

The following are strategies that can make the code more testable, and allow control over the side effects

Use a command pattern

Consider splitting the code so that first ‘we decide what we are going to do’ and then ‘we do it’. This then allows the code that is doing the business logic of working out what sideeffects are needed to be tested separately from the ‘and now we do it’.

Use a layer of abstraction

Let us suppose the side effect is ‘printing to a log’ or ‘sending a metric’. The following is a sample from drop wizard

private final static Logger log = Logger.getLogger(MyLogger.class.getName());
public void myMethod(){
    Jdbc.update(" <some sql>")
    log.info("Written to database")
    Meter meter = metricRegistry.meter("databaseMeter");
    meter.mark(1);
}

This is quite typical where we ‘do some database things’ (side effects) and in the same method log our results and notify the metrics system.

This code is really hard to test! Let’s work out how we could simplify it. This is not about ‘whether its ‘right to use sql or prepared statements’, just about how we test what we have.

private final  ILogger log; // configured by dependency injection
private final IJdbcUpdate jdbc; // our interface that decouples us from the JDBC framework we are using
private final IMetrics metrics; // our interface that decouples us from the metrics library we are using
final static String metricName = "databaseMeter"
final static String sql = " <some sql>"
public void myMethod(){
    jdbc.update()
    log.info("Written to database")
    metrics.mark(metricName, 1);
}

Observe that now the code is really easy to test. And further we have decoupled ourselves from the libraries that we are using: a double bonus.

The test would now look like (assuming constructor injection)

   Ilogger log = makeMock(ILogger.class);
   IJdbcUpdate jdbc = makeMock(IJdbcUpdate.class);
   IMetrics metrics = makeMock(IMetrics.class);
   
   MyClass myClass = new MyClass(log, jdbc, metrics);
   myClass.myMethod();
   verify(jdbc, times(1)).update(MyClass.sql)
   verify (log, times(1)).log("Written to database")
   verify(metrics, times(1).mark(MyClass.metricName))

For extra points this could easily be split into three tests: one that checks the jdbc is called correctly, another that checks a log is called and another for the metrics.