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.
This lesson assumes that:
- You are familiar with DbUnit
- You can configure your integration tests which use JUnit 4.
- You can write integration tests for Spring web applications by using JUnit 4.
- You are familiar with Spock Framework
- You can configure the system under specification when you write integration tests with Spock Framework
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
@DatabaseSetupannotation to a specification class or to a feature method. - We have to add the
@ExpectedDatabaseannotation 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.