Not a member of GistPad yet?
Sign Up,
it unlocks many cool features!
- import com.btc.mft.balanceenergy.infrastructure.service.DatabaseBalEnergyConfigQueryService
- import com.btc.mft.calculationprocedure.domain.service.BeDemandAssignmentService
- import com.btc.mft.calculationprocedure.domain.service.CalculationProcedureConfigQueryService
- import com.btc.mft.calculationprocedure.infrastructure.client.NetFlowDistributionSolverClient
- import com.btc.mft.calculationprocedure.infrastructure.client.TimeoutSolverApiBuilder
- import com.btc.mft.commons.domain.model.GasDay
- import com.btc.mft.commons.temporal.DateUtils
- import com.btc.mft.demandassessment.trigger.DemandAssessmentRestController
- import com.btc.mft.masterdata.domain.service.MasterDataConfigQueryService
- import com.btc.mft.node.domain.model.NodeId
- import com.btc.mft.shared.logging.domain.service.MftLogger
- import com.btc.mft.shared.state.ThreadUtils
- import com.btc.mft.system.domain.model.LockKey
- import com.btc.mft.system.domain.service.LockHolder
- import com.btc.mft.system.domain.service.LockingService
- import com.btc.mft.test.TestUtils
- import com.btc.mft.test.annotation.TimeBubble
- import com.btc.mft.timeseries.domain.service.TimeSeriesLockService
- import com.btc.mft.timeseries.domain.service.TimeSeriesWriteProtectionQueryService
- import com.btc.mft.zone.domain.model.GasQuality
- import com.btc.mft.zone.domain.model.Zone
- import com.hazelcast.cp.lock.FencedLock
- import com.the.mftsolver.rest.api.SolverApi
- import com.the.mftsolver.rest.model.ProblemDto
- import org.springframework.http.HttpStatus
- import org.springframework.http.ResponseEntity
- import spock.lang.*
- import spock.util.time.MutableClock
- import java.time.Duration
- import java.time.ZonedDateTime
- // Please install Intellij IDEA plugin 'Spock Framework Enhancements' and configure your preferred color for spock labels/blocks
- // see documentation: https://spockframework.org/spock/docs/2.0/all_in_one.html#_spock_primer
- // [optional] shows which classes are the target of this spec or with multiple @Subject(<clazz>)
- // [optional]
- @Title("This is a more readable name for this demonstrative specification")
- // [optional]
- @Narrative("""
- As a developer in this project
- I want new developer to have an good introduction into spock
- So they can write good specification early on
- """)
- // [optional] also on feature method possible
- @Issue("https://oge.atlassian.net/browse/MFT-000")
- // [optional] also on feature method possible
- @See("https://oge.atlassian.net/wiki/spaces/MFT")
- // enforce test execution in declaration order
- @Stepwise
- // all spock tests (specifications) must extends spock.lang.Specification
- // instance field - not shared between feature methods unless annotated with @Shared
- @Shared
- // fixture methods
- // note for inheritance: supers setup is called first, subs cleanup is called first
- // runs once - before the first feature method (JUnit 4/5: @BeforeClass/@BeforeAll, TestNG: @BeforeClass)
- // runs before every feature method (JUnit 4/5: @Before/@BeforeEach, TestNG: @BeforeMethod)
- assert obj // explicit condition besides implicits in 'then' and 'expect' blocks
- }
- // runs after every feature method (JUnit 4/5: @After/@AfterEach, TestNG: @AfterMethod)
- // runs once - after the last feature method (JUnit 4/5: @AfterClass/@AfterAll, TestNG: @AfterClass)
- // feature methods (JUnit: test method)
- expect: // 'stimulus + response phase': use 'expect' block if when-then block are more of a overhead
- 1 + 1 == 2 // a condition is a plain boolean expression
- }
- given: // 'setup phase': use 'given' block to setup your feature method, should not be preceded by others blocks
- // when-then pair always comes together
- when: // 'stimulus phase': contains arbitrary code which is the test subject
- then: // 'response phase': contains only conditions, exception conditions, interactions and variable defs as expectation
- result == 4
- // multiple when-then blocks can be present in a feature method
- when:
- then:
- result2 == 4
- }
- given:
- when:
- list.remove(0)
- then:
- list == [2, 3, 4] // list can be tested directly
- }
- given:
- when:
- list.remove(20)
- then:
- thrown(IndexOutOfBoundsException) // exception condition: other conditions and when-then blocks can follow that
- // alternative to use the exception instance:
- //IndexOutOfBoundsException e = thrown()
- }
- given:
- when:
- list.remove(0)
- then:
- notThrown(IndexOutOfBoundsException) // exception condition: feature method will fail if any other exception is thrown
- }
- @Unroll("[#iterationIndex] #a divided by zero fails")
- // use this to give iteration a different name
- when:
- a / 0
- then:
- where:
- a << [1, 2, 3, 4, 5] // this is a data pipe we use if multiple inputs expected to behave the same
- // or as data table
- // a | _
- // 1 | _
- // 2 | _
- // 3 | _
- // 4 | _
- // 5 | _
- }
- //@Rollup // use this if you do not want each iteration logged separately
- // parameter declaration is not necessary BUT it helps with IDE support
- expect:
- where:
- // this is a data table we use if we have multiple inputs and/or expected values for each iteration
- // syntax: input | input || expected value
- a | b || c
- 1 | 2 || 1
- 2 | 2 || 4
- 3 | 2 || 9
- // in data tables you can access the own column vars, other data table vars, static fields and @Shared instance fields
- }
- // mocks can be used for mocking and stubbing, stubs only for stubbing. use stubs if mocking is not needed
- given:
- DatabaseBalEnergyConfigQueryService balEnergyConfigurationService = Mock() // mocks are useful to observer/check the code flow
- when:
- balEnergyConfigurationService.getMessageMaximumAgeInMinutes()
- balEnergyConfigurationService.getCapacityQuantitiesFollowUpTime()
- balEnergyConfigurationService.getCapacityQuantitiesFollowUpTime()
- balEnergyConfigurationService.getCapacityQuantitiesFollowUpTime()
- then:
- 1 * balEnergyConfigurationService.getMessageMaximumAgeInMinutes() // only one call
- (2.._) * balEnergyConfigurationService.getCapacityQuantitiesFollowUpTime() // at least two calls (_ is any)
- }
- given:
- MasterDataConfigQueryService masterDataConfigQueryService = Mock()
- BeDemandAssignmentService beDemandAssignmentService = Mock() // preferred style since it may give better IDE support
- TimeSeriesWriteProtectionQueryService timeSeriesWriteProtectionQueryService = Mock()
- when:
- //def LongDefault = balEnergyConfigurationService.getCapacityQuantitiesFollowUpTime()
- def optionalDefault = beDemandAssignmentService.getAssignedZoneId(GasQuality.H_GAS, ZonedDateTime.now(), ZonedDateTime.now())
- // more then around five conditions => check if you test different features you may should split in smaller feature methods
- then:
- longDefault == 0
- //LongDefault == null
- customClassDefault == null
- !booleanDefault
- optionalDefault == null
- listDefault == null
- }
- given:
- when:
- def optionalDefault = beDemandAssignmentService.getAssignedZoneId(GasQuality.H_GAS, ZonedDateTime.now(), ZonedDateTime.now())
- then:
- longDefault == 0
- LongDefault == 0
- customClassDefault
- customClassDefault.getValue() == 0
- !booleanDefault
- optionalDefault.isEmpty()
- }
- given: "a procedure id" // we can label any block to provide additional information
- Long procedureId = 0L
- and: "a stubbed rest controller" // using labels and 'and' is preferred
- controller.verifyCalculationProcedureSubstituteValues(procedureId, []) >> ResponseEntity.accepted().build()
- expect: "calling the stubbed method behave as expected"
- controller.verifyCalculationProcedureSubstituteValues(procedureId, []) == ResponseEntity.accepted().build()
- }
- given: "a procedure id"
- Long procedureId = 0L
- and: "a stubbed rest controller returning always notFound except on the first call"
- controller.verifyCalculationProcedureSubstituteValues(procedureId, []) >>> [
- ResponseEntity.accepted().build(),
- ResponseEntity.notFound().build()
- ]
- when: "we call it three times"
- then: "the first response is accepted"
- callOne == ResponseEntity.accepted().build()
- and: "the next responses are notFound"
- callTwo == ResponseEntity.notFound().build()
- callThree == ResponseEntity.notFound().build()
- }
- /*
- more one stubbing (in short)
- details: https://spockframework.org/spock/docs/2.0/all_in_one.html#_stubbing
- computing result:
- subscriber.receive(_) >> { args -> args[0].size() > 3 ? "ok" : "fail" }
- or
- subscriber.receive(_) >> { String message -> message.size() > 3 ? "ok" : "fail" }
- side effects: (can contain more abitrary code)
- subscriber.receive(_) >> { throw new InternalError("ouch") }
- chaining resulst:
- subscriber.receive(_) >>> ["ok", "fail", "ok"] >> { throw new InternalError() } >> "ok"
- default non-null result:
- subscriber.receive(_) >> _
- combining mocking and stubbing: (must be in one statement)
- 1 * subscriber.receive("message1") >> "ok"
- */
- given: "a stubbed lockingService always returning a ignored lock with a given key"
- lock.getName() >> key.getKey()
- }
- and: "a real timeSeriesLockService"
- when: "we call to create a lock for distrivution"
- LockHolder lock = lockService.lockDistribution(GasDay.now())
- then: "this lock contains the expected key"
- checkKeyExists(lock)
- // or use 'with' to check multiple properties of an object, which has the same behavior like a helper methods
- with(lock) {
- metaClass != null
- }
- // with you want to check all assertions once use verifyAll
- verifyAll(lock) {
- metaClass != null
- }
- }
- // in this case we must use the assert keyword, also test fails on first failed assertion
- assert lock
- }
- given: "a clock to play with"
- and: "we put the clock into the application"
- TestUtils.startTimeBubble(clock)
- and: "we remember a birthday"
- ZonedDateTime birthday = DateUtils.from(2021, 7, 10, 0, 0, 0)
- expect: "its not the birthday day and threads execution time is now"
- DateUtils.now() != birthday
- ThreadUtils.executionTimestamp() == DateUtils.now()
- when: "we tell the clock to be a day later"
- clock + Duration.ofDays(1)
- then: "it's birthday time but thread execution time is kept to now"
- DateUtils.now() == birthday
- ThreadUtils.executionTimestamp() != birthday
- cleanup: "is important after playing with the application clock"
- TestUtils.stopTimeBubble()
- }
- // @TimeBubble is also allowed on spec (class) level
- @TimeBubble(year = 2021, month = 4, day = 4, hour = 5, minutes = 11, seconds = 0)
- expect:
- DateUtils.now() == DateUtils.from(2021, 4, 4, 5, 11, 0)
- }
- given: "a timeout is configured"
- calculationProcedureConfigurationService.getSolverTimeoutInSeconds() >> timeoutInSeconds
- and: "a solverApi mock is provided"
- SolverApi solverApi = Mock()
- and: "our test subject: solver client"
- calculationProcedureConfigurationService,
- timeoutSolverApiBuilder
- )
- when: "solver client internal method is called"
- // we can use 'doInTimeBubble' instead of manual set start and end of fixed time,
- // if we do not have checks in 'then' block which require the fixed time
- TestUtils.doInTimeBubble(fixedTime, time -> {
- uut.callSolverWithTimeout(Mock(ProblemDto), timeoutInSeconds)
- })
- then: "solver api is called with correct timeout"
- where: "different starting time are tested"
- timeoutInSeconds | fixedTime || expectedTimeoutTime
- 100L | DateUtils.from(2021, 4, 4, 5, 11, 0) || DateUtils.from(2021, 4, 4, 5, 12, 40).toOffsetDateTime().toString()
- 100L | DateUtils.from(2021, 12, 31, 23, 59, 0) || fixedTime.plusSeconds(timeoutInSeconds).toOffsetDateTime().toString()
- }
- /*
- Use @Retry(<retryCount>) if you have a weird test case that sometimes fails due to non-deterministic integration test.
- <rertyCount> default is: 3
- Use @Timeout(<threshold>) to stop an iteration which may can take to long
- */
- }
RAW Paste Data
Copied
