application.conf: db.connection.readurl=jdbc:postgresql://localhost:5432/tripvis db.connection.writeurl=jdbc:postgresql://localhost:5432/tripvis db.connection.username=tripvis db.connection.password=YeahRight
@Singleton public abstract class PersistDB { @Inject NinjaProperties ninjaProperties; public static final String DB_READ_SERVER_URL_PROP = "db.connection.readurl"; public static final String DB_READ_SERVER_URL_DEFAULT = "jdbc:postgresql://localhost:5432/tripvis"; public static String dbReadServerUrl = DB_READ_SERVER_URL_DEFAULT; // etc @Inject protected void loadProps() throws BrokenSystemException { if (ninjaProperties != null) { dbReadServerUrl = ninjaProperties.getWithDefault(DB_READ_SERVER_URL_PROP, DB_READ_SERVER_URL_DEFAULT); // etc } else { logger.log(Level.SEVERE, "No NinjaProperties created, database config is stuffed"); } } }
The injection saves creating the object which deals with NinjaProperties and loading the file, which is very handy. However, there is a downside. When running JUnit tests the Ninja framework isn't loaded in the normal way, so the relevant classes aren't constructed by the framework but by the test object. (Yes, yes, I know, my example accesses a database, so this is stretching "unit" a bit.) This means that the injection no longer happens for free. Indeed, you're stuck with the hard-coded defaults. This is an issue in several ways:
- The test doesn't exercise the loading of the properties at all.
- If the properties required by the test environment change then the code has to change.
- If the developers' unit testing environment and the CI environment aren't the same then this approach stops working.
public abstract class PersistDB { public void fakeLoadProps(NinjaProperties props) throws BrokenSystemException { logger.log(Level.INFO, "loading fake properties"); ninjaProperties = props; loadProps(); }
public class UsersDBTest { static UsersDB db; @BeforeClass public static void createDBObject() throws BrokenSystemException { UsersDBTestFakeProps props; props = new UsersDBTestFakeProps(); props.put("db.readserver", "localhost"); props.put("db.writeserver", "localhost"); props.put("db.connection.url", "jdbc:postgresql://localhost:5432/tripvis"); props.put("db.connection.username", "dan"); props.put("db.connection.password", "YoHoHo"); db = new UsersDB(); db.fakeLoadProps(props); }
public class UsersDBTestFakeProps implements NinjaProperties { private HashMapprops; public UsersDBTestFakeProps() { props = new HashMap<>(); } public void put(String key, String value) { props.put(key, value); } @Override public String get(String key) { return props.get(key); } @Override public String getWithDefault(String key, String defaultValue) { if (props.containsKey(key)) { return get(key); } else { return defaultValue; } } // etc for any other methods needed for the tests
More work than injection, but mostly confined to the tests and not that tricksy.
Note, this is just a fake - a simple class that fulfils the interface, but takes a big short-cut. It is marginally more clever than the canned response from a stub, but doesn't go as far as checking that the right calls are made like a mock would. The mock approach would help test that the right queries are being made, but at the moment they're all bundled together in the loadProps() method so we're a good refactoring step away from that right now. For those that aren't familiar with the mock / fake / stub distinction I'd recommend reading Martin Fowler and Emily Bache.
Recalling the issues from before, I'm left with some caveats:
- The test doesn't touch the properties file at all. You still need to do some system testing to do that. But this approach flags an issue in code (rather than config) faster than that will.
- If the properties required by the test environment change then the test code has to change. Could be worse. If this is really an issue then the fake could load a file.
- If the developers' unit testing environment and the CI environment aren't the same then the code above needs tweaked to spot the environment and load different values into the fake. A bit fiddly and loading a file might start looking clever as soon as this gets at all involved, e.g. with more than one developer.
No comments:
Post a Comment