Using DbUnit With Spock Framework

After we have finished this lesson, we

  • Know how we can initialize our database into a known state before our feature methods are run.
  • Can write assertions for the data found from our database.

Initializing Our Database Into a Known State

As we remember , we can configure the DbUnit data sets that initialize our database into a known state by using the @DatabaseSetup annotation. When we are writing integration tests with Spock Framework, we can initialize our database into a known state by using one of these two options:

First, if we want that all feature methods of our specification class use the same DbUnit data sets, we have to annotate our specification class with the @DatabaseSetup annotation.

After we have configured the DbUnit data sets that are shared by all feature methods, the source code of our specification class looks as follows:

 1import com.github.springtestdbunit.DbUnitTestExecutionListener
 2import com.github.springtestdbunit.annotation.DatabaseSetup;
 3import com.github.springtestdbunit.annotation.DbUnitConfiguration
 4import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
 5import org.junit.experimental.categories.Category
 6import org.springframework.beans.factory.annotation.Autowired
 7import org.springframework.test.context.ActiveProfiles
 8import org.springframework.test.context.ContextConfiguration
 9import org.springframework.test.context.TestExecutionListeners
10import org.springframework.test.context.web.WebAppConfiguration
11import org.springframework.test.web.servlet.setup.MockMvcBuilders
12import org.springframework.web.context.WebApplicationContext
13import spock.lang.Specification
14
15import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity
16
17@ContextConfiguration(classes = [IntegrationTestContext.class])
18@WebAppConfiguration
19@TestExecutionListeners(value = DbUnitTestExecutionListener.class,
20        mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS
21)
22@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader.class)
23@Category(IntegrationTest.class)
24@DatabaseSetup([
25	'/com/testwithspring/master/users.xml'
26])
27@ActiveProfiles(Profiles.INTEGRATION_TEST)
28class ExampleSpec extends Specification {
29
30    @Autowired
31    WebApplicationContext webAppContext
32
33    def mockMvc
34
35    def setup() {
36        mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext)
37                .apply(springSecurity())
38                .build()
39    }
40
41    def 'feature method one'() {
42
43    }
44
45    def 'feature method two'() {
46
47    }
48}

Second, if we want that the feature methods of our specification class use different DbUnit data sets, we have to annotate our feature methods with the @DatabaseSetup annotation.

After we have configured the DbUnit data sets that are used by our feature methods, the source code of our specification class looks as follows:

 1import com.github.springtestdbunit.DbUnitTestExecutionListener
 2import com.github.springtestdbunit.annotation.DatabaseSetup;
 3import com.github.springtestdbunit.annotation.DbUnitConfiguration
 4import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
 5import org.junit.experimental.categories.Category
 6import org.springframework.beans.factory.annotation.Autowired
 7import org.springframework.test.context.ActiveProfiles
 8import org.springframework.test.context.ContextConfiguration
 9import org.springframework.test.context.TestExecutionListeners
10import org.springframework.test.context.web.WebAppConfiguration
11import org.springframework.test.web.servlet.setup.MockMvcBuilders
12import org.springframework.web.context.WebApplicationContext
13import spock.lang.Specification
14
15import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity
16
17@ContextConfiguration(classes = [IntegrationTestContext.class])
18@WebAppConfiguration
19@TestExecutionListeners(value = DbUnitTestExecutionListener.class,
20        mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS
21)
22@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader.class)
23@Category(IntegrationTest.class)
24@ActiveProfiles(Profiles.INTEGRATION_TEST)
25class ExampleSpec extends Specification {
26
27    @Autowired
28    WebApplicationContext webAppContext
29
30    def mockMvc
31
32    def setup() {
33        mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext)
34                .apply(springSecurity())
35                .build()
36    }
37
38	@DatabaseSetup([
39		'feature-method-one.xml'
40	])
41    def 'feature method one'() {
42
43    }
44
45	@DatabaseSetup([
46		'feature-method-two.xml'
47	])
48    def 'feature method two'() {
49
50    }
51}

Next, we will find out how we can write assertions for the data found from our database.

Writing Assertions for the Data Found From Our Database

We can write assertions for the data found from the database by annotating our feature methods with the @ExpectedDatabase annotation.

Let’s verify that the system under specification saves a new task to the database. After we have written the required assertions, the source code of our specification class looks as follows:

 1import com.github.springtestdbunit.DbUnitTestExecutionListener
 2import com.github.springtestdbunit.annotation.DatabaseSetup;
 3import com.github.springtestdbunit.annotation.DbUnitConfiguration
 4import com.github.springtestdbunit.annotation.ExpectedDatabase
 5import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
 6import org.junit.experimental.categories.Category
 7import org.springframework.beans.factory.annotation.Autowired
 8import org.springframework.test.context.ActiveProfiles
 9import org.springframework.test.context.ContextConfiguration
10import org.springframework.test.context.TestExecutionListeners
11import org.springframework.test.context.web.WebAppConfiguration
12import org.springframework.test.web.servlet.setup.MockMvcBuilders
13import org.springframework.web.context.WebApplicationContext
14import spock.lang.Specification
15
16import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity
17
18@ContextConfiguration(classes = [IntegrationTestContext.class])
19@WebAppConfiguration
20@TestExecutionListeners(value = DbUnitTestExecutionListener.class,
21        mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS
22)
23@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader.class)
24@Category(IntegrationTest.class)
25@DatabaseSetup([
26        '/com/testwithspring/master/users.xml',
27        '/com/testwithspring/master/no-tasks-and-tags.xml'
28])
29@ActiveProfiles(Profiles.INTEGRATION_TEST)
30class CreateTaskSpec extends Specification {
31
32    @Autowired
33    WebApplicationContext webAppContext
34
35    def mockMvc
36
37    def setup() {
38        mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext)
39                .apply(springSecurity())
40                .build()
41    }
42
43    @ExpectedDatabase(value = 'create-task-as-user-expected.xml',
44            assertionMode = DatabaseAssertionMode.NON_STRICT
45    )
46    def 'Create task as a registered user'() {
47
48    }
49}

The DbUnit data set that specifies the expected content of our database looks as follows:

 1<dataset>
 2    <tasks id="1"
 3           assignee_id="[null]"
 4           closer_id="[null]"
 5           creation_time="2016-12-03 21:14:28.0"
 6           creator_id="1"
 7           description="This example contains integration tests"
 8           modification_time="2016-12-03 21:14:28.0"
 9           modifier_id="1"
10           resolution="[null]"
11           title="Write example application"
12           status="OPEN"
13           version="0"/>
14    <tags/>
15</dataset>

When we write assertions for the data found from our database, we can use one of these two options:

First, we can write one feature method that specifies the behavior of the system under specification and writes assertions for the data found from our database. If we use this approach, we cannot use DbUnit data sets that assert one logical concept. We have to use one data set that specifies the expected content of all asserted database tables. This can be a problem if our data set becomes so long that it’s unreadable.

Second, we can write multiple feature methods. One feature method specifies the behavior of the system under specification and the other feature methods write assertions for the data found from our database. If we use this approach, we should write one specification class per one use case because otherwise our specification classes become too messy and the created documentation doesn’t make any sense.

This course uses the first option because I think that it’s a decent compromise between clean documentation and clean tests. The next lessons of this topic will demonstrate what I mean. However, it’s also important to understand that we have another option. We shouldn’t be afraid to use it if it makes more sense to us.

We can now initialize our database into a known state and write assertions for the data found from our database. Let’s summarize what we learned from this lesson.

Summary

This lesson has taught us three things:

  • We can add the @DatabaseSetup annotation to a specification class or to a feature method.
  • We have to add the @ExpectedDatabase annotation to a feature method.
  • Most of the time we should write only one feature method that specifies the behavior of the system under specification and writes assertions for the data found from our database.

Get the source code from Github