Search code examples
scalafunctional-testingscalatestspray

How to cut a long ScalaTest spec to pieces


I'm testing a REST API, and the code goes like this:

  1. Setting up stuff, populating a database using PUSH calls
  2. Testing API a
  3. Testing API b ...

The code is currently in one rather huge FlatSpec:

class RestAPITest extends FlatSpec
  with Matchers
  with ScalatestRouteTest
  with SprayJsonSupport

I would like to chop the "Testing API a/b/..." parts out, to have the code more manageable. Trying to do that seems like a no-no: what's the type of it - how to pass that on, etc. etc.

So, what's the recommended way to go about such stuff.

The a/b/... tests could be run in parallel, once the basic setup has succeeded.

I'm currently using assume within the a/b/... tests to make them cancel if the initialization failed.

Should I look at "fixtures" or what for this? Have tried BeforeAndAfterAll earlier, but didn't really get it working for me.

Thanks for the pointers / opinions. How do you keep your test suites short?


Solution

  • Adding as a new answer, so the differences are clear and the discussion above need not be removed. If I didn't do any typos, this should work (I did test it, and adopted in my project).

    import org.scalatest._
    
    /*
    * Mix this trait into any specs that need 'TestA' to have been run first.
    */
    trait TestAFirst {
    
      // Reading a 'TestA' object's field causes it to be instantiated and 'TestA' to be executed (but just once).
      //
      val testASuccess = TestA.success
    }
    
    /*
    * 'TestA' gets instantiated via the companion object explicitly (thus @DoNotDiscover)
    * and creates a success value field. Otherwise, it's a test just like any other.
    */
    @DoNotDiscover
    class TestA private extends FlatSpec {
      private var success = false   // read once, by the companion object
    
      behavior of "Root class"; {
        it should "run prior to any of the B,C classes" in {
    
          assert(true)    // ... A tests
    
          success = true
        }
      }
    }
    
    object TestA {
      val success = {
        val o= new TestA
        o.execute
        o.success   // getting a value from the executed test ('.execute()' itself doesn't provide a status)
      }
    }
    
    class TestB extends FlatSpec with TestAFirst {
    
      behavior of "class B"; {
        it should "run after A has been run" in {
          assume(testASuccess)
    
          assert(true)    // ... B tests
        }
      }
    }
    
    class TestC extends FlatSpec with TestAFirst {
    
      behavior of "class C"; {
        it should "run after A has been run" in {
          assume(testASuccess)
    
          assert(true)    // ... C tests
        }
      }
    }