<@U0B94AR3J> can you give an example?
# spek
r
@robfletcher can you give an example?
r
for example the subject has a dependency that needs to be managed by the main spec — a mock maybe — there doesn’t seem to be a way to pass that to a
subject {}
block in another spec that wants to use
itBehavesLike
so that it can be used by the constructor of the class-under-test
There doesn’t seem to be any way I’d be able to pass the
deadLetterCallback
which is created and managed by
QueueSpec
to the constructor of
RedisQueue
if I was using a
subject
block there.
So I’ve solved it by making
QueueSpec
an abstract class and
RedisQueueSpec
extend it but this seems to be exactly the kind of case
itBehavesLike
is designed for.
r
the main spec will completely override the included subject spec (
itBehavesLike
). The included spec doesn't really now if it's used that way. I know there's currently a bug with
itBehavesLike
- going to release the fix today.
you might want something like this?
Copy code
abstract class QueueSpec: SubjectSpek<Queue>({
  ...
})

object RedisQueueSpec: SubjectSpek<RedisQueue>({
  subject { 
    RedisQueue(...)
  }
  
  // i know it's ugly, but we need to mark it as abstract
  // so spek will ignore it.
  // this will be fix when we add `@IgnoreSpek` <https://github.com/JetBrains/spek/issues/200>
  // itBehavesLike(QueueSpec)
  itBehavesLike(object: QueueSpec() {})
})
r
ah, I see. Interesting
I’ll have a play with it once the new release fixing the subject target is out and see if I can figure out a good pattern
r
1.1.2 is out in bintray 🙂
👍 1
r
I still can’t figure out any way to pass something to the subject constructor that is defined in the “parent” spec.
r
I think you're using subject specs wrong. Since the parent overrides the included specs subject. Do you want to pass something else?
r
well, the way it works now it’s impossible to use a subject that has a constructor that needs anything the test also needs to know about
for example, a mock collaborator
in that case you just can’t use SubjectSpek at all as far as I can see
you can do so in the “main” spec but there’s no way for another spec that wants to use
itBehavesLike
to construct its subject appropriately
r
You could do something like:
Copy code
abstract class QueueSpec(collaborator1: Mock): SubjectSpek<Queue>({
  ... // mock collaborator1
})

object RedisQueueSpec: SubjectSpek<RedisQueue>({
  subject { 
    RedisQueue(collaborator1)
  }
  
  // i know it's ugly, but we need to mark it as abstract
  // so spek will ignore it.
  // this will be fix when we add `@IgnoreSpek` <https://github.com/JetBrains/spek/issues/200>
  // itBehavesLike(QueueSpec)
  itBehavesLike(object: QueueSpec(collaborator1) {})
})
r
In that scenario RedisQueueSpec has no way to see
collaborator1
r
@robfletcher why not you can declare it as a local variable? or by using memoized `val collaborator1 by memoized { // build collaborator1 here }
r
I’m going to write up an example when I have a moment, I think we’re talking at cross purposes
r
sure, that will definitely be great. 🙂
r
r
Tried giving it a go: https://gist.github.com/raniejade/762bb030fb2c03e6aa6e8adda50cfde8. I feel like that subject is not suited for this kind of use-case.
r
SubjectSpek is working well for me where I don’t have multiple implementations I want to apply the same tests to — i.e. where I’m not using
itBehavesLike
. But I can’t think of many scenarios where I would want to use that and wouldn’t also be passing things to the subject constructor
r
Do you want something like this?
Copy code
interface Collaborator: () -> Unit {
  fun reset()
  fun isInvoked(): Boolean
}

open class Thing(protected val fn: () -> Unit) {
  fun doIfEven(n: Int) {
    if (n % 2 == 0) fn()
  }
}

@IgnoreSpek
object ThingSpec: SubjectSpek<Thing>({
    val mockFn by memoized()
  
    // subject {
        // constructor of subject depends on collaborator
        // Thing(mockFn)
    //}

    describe("doIfEven") {
        given("an even parameter") {
            afterGroup(mockFn::reset)

            on("calling doIfEven") {
                subject.doIfEven(2)
            }

            it("invokes the function") {
                assertTrue("mock was not invoked", mockFn.invoked)
            }
        }

        given("an odd parameter") {
            afterGroup(mockFn::reset)

            on("calling doIfEven") {
                subject.doIfEven(1)
            }

            it("does not invoke the function") {
                assertFalse("mock should not have been invoked", mockFn.invoked)
            }
        }
    }
})

class ExtendedThing(fn: () -> Unit) : Thing(fn)

class ExtendedThingSpec : SubjectSpek<ExtendedThing>({
    val mockFn by memoized {
      object : Collaborator {
        var invoked = false

        override fun invoke() {
            invoked = true
        }

        override fun reset() {
            invoked = false
        }

        override fun isInvoked() = invoked
      }
    }
  
    subject {
        ExtendedThing(mockFn)
    }

    itBehavesLike(ThingSpec)
})