This guide is targeted at using jfixture as a tool for tests, but it can also be use for other data loading tasks that aren't test related (e.g. populating a roles table in an RBAC system).
A fixture is an arbitrary grouping of rows across various tables. It serves as a means to organize your test data. You could have separate fixtures for each test method or for each conceptual group of test methods, for instance. Fixture names are the highest level elements in a yaml file:
fixture1: someTable: column: value1 # db will supply its default for any column not specified in the yaml anotherTable: anotherColumn: value2 fixture2: someTable: column: value3 yetAnotherTable: yetAnotherColumn: value4
The above example uses "anonymous rows" (one row specified per table), but it's often convenient to specify multiple rows for the same table inside one fixture. You can get values from anonymous rows with the FixtureController#get() method.
fixtureController.get("main", "userFixture", "user", "name");
The two following yaml examples show more ways to specify rows that allow multiple rows per table.
userFixture: user: userWithoutPassword: name: noPassword userWithPassword: name: hasPassword password: secret
This structure lets you give names to rows. This makes it easier to tell what the different rows mean, especially when you have many rows with subtle but important distinctions between them. You get values for named rows out of the controller by supplying an extra parameter to get():
fixtureController.get("main", "userFixture", "user", "userWithoutPassword", "name");
If you want to supply many rows but don't really care to refer to them each by specific names, you can use a list of rows.
userFixture: user: - name: noPassword - name: hasPassword password: secret
You can still access the data by specifying a 0-indexed position in the list, of course, but that's less self-documenting than explicitly named rows. Sometimes this is a reasonable trade-off, though, like when you're testing bulk manipulation of repetetive data.
fixtureController.get("main", "userFixture", "user", 0, "name");
It's often convenient to be able to set a value in one row to a value that you've already specified in another row. If you have a database design that calls for some denormalization (e.g. for performance or legacy compatibility), this can help make your fixtures robust and easy to update. In the following examples, assume that the address table needs to have a duplicate copy of the user's name.
userFixture: user: userId: 1 name: someUser address: userId: 1 name: ${userFixture.user.name}
The ${} part is a reference to a value in another row. Since we are accessing a row in the same fixture, we could have also used a shortcut by replacing 'userFixture' with 'this': ${this.user.name}. In this particular example, there's just one row in the user table (making this an 'anonymous' row). The following examples show how to refer to named and indexed rows as well as accessing other fixtures.
indexedUserRows: user: - userId: 1 name: index0 - userId: 2 name: index1 address: - userId: ${namedUserRows.user{joeUser}.userId} name: ${namedUserRows.user{joeUser}.name} - userId: ${namedUserRows.user{janeUser}.userId} name: ${namedUserRows.user{janeUser}.name} namedUserRows: user: joeUser: userId: 3 name: joe janeUser: userId: 4 name: jane address: - userId: ${indexedUserRows.user[0].userId} name: ${indexedUserRows.user[0].name} - userId: ${indexedUserRows.user[1].userId} name: ${indexedUserRows.user[1].name}
There is one other bit of syntax for references that applies identically to all three reference types (anonymous, named and indexed). If you are referring to a row in another database, use the database name and a slash at the start of the reference like this: ${otherDb/fixture.table{rowName}.column}. The 'otherDb' string should be whatever identifier you used when adding the DataSource to the controller.
As you probably noticed, in the previous example, we were hard-coding the userId values. This is not desirable for obvious reasons. If user.userId is a generated key column (auto increment in MySQL, serial in PostgreSQL), instead of specifying the id by hand you could use 'userId: ${autoId}' to tell jfixture to not supply that column in the SQL INSERT statement and inspect the results to figure out what id the database generated for that row. Let's revisit the previous example using ${autoId} instead of hard-coded ids.
indexedUserRows: user: - userId: ${autoId} name: index0 - userId: ${autoId} name: index1 address: - userId: ${namedUserRows.user{joeUser}.userId} name: ${namedUserRows.user{joeUser}.name} - userId: ${namedUserRows.user{janeUser}.userId} name: ${namedUserRows.user{janeUser}.name} namedUserRows: user: joeUser: userId: ${autoId} name: joe janeUser: userId: ${autoId} name: jane address: - userId: ${indexedUserRows.user[0].userId} name: ${indexedUserRows.user[0].name} - userId: ${indexedUserRows.user[1].userId} name: ${indexedUserRows.user[1].name}
This illustrates another important point. Inserting rows in order from top to bottom will not work since the first group of address rows needs to know the ids of user rows that are inserted later on. Instead, jfixture constructs a graph of all row dependencies and inserts them in an order that will insert a row before any other rows that depend on it. This also works across databases, not just across fixtures.
Jfixture is not very complicated to use once you have your fixtures set up. The code in the overview covers most of it. The only other bit of externally visible API is the syntax for accessing values in named and indexed rows:
// anonymous fixtureController.get("dbname", "fixture", "table", "column"); // named fixtureController.get("dbname", "fixture", "table", "rowName", "column"); // indexed fixtureController.get("dbname", "fixture", "table", 23, "column");
If you're not using raw JDBC, you may not have easy access to a DataSource. Hibernate, for instance, provides the Work interface as a means of accessing the underling JDBC Connection but doesn't expose the underlying DataSource directly. The ConnectionProvider interface provides a way to work with things like Hibernate's SessionFactory and Work interfaces without needing to access a DataSource. There's already an implementation for Hibernate: HibernateConnectionProvider in the jfixture-hibernate module. You would use it in FixtureController#addDb(String, ConnectionProvider) like this:
fc.addDb("someDb", new HibernateConnectionProvider(getSessionFactory()));
If you need to write a custom implementation to work with your ORM or other DB system, look at the implementation of HibernateConnectionProvider and DataSourceConnectionProvider.