Search code examples
scalaunit-testingmockingscalatestscalamock

ScalaTest with mocked object


I found some simple examples, but nothing works.

a model:

class Product() {
  var price: Int = 0
  var stock: Int = 0

  def addPrice(price: Int): Int = {
    this.price = price
    this.price
  }

  def addStock(qty: Int): Int = {
    this.stock += qty
    this.stock
  }
}

and my test class

classProductSpec extends AnyFlatSpec with MockFactory with OneInstancePerTest {
  val m = mock[Product]

  // suites (test examples)
  inAnyOrder {
   (m.addPrice _).expects(40).returns(40).once
   inSequence {
     (m.addStock _).expects(20).returns(20).once
     (m.addStock _).expects(10).returns(30).once
   }
  }

  // tests
  it should "set price" in {
    assert(m.addPrice(40) == 40)
  }

  it should "add new qty. Step 1" in {
    assert(m.addStock(20) == 20)
  }

  it should "add new qty. Step 2" in {
    assert(m.addStock(10) == 30)
  }
}

Every time, the error is:

Expected:
  inAnyOrder {
     inAnyOrder {
     <mock-1> Product.addPrice(40) once (never called - UNSATISFIED)
    ...
  }
}

If I run only one suite and one assert, it works:

(m.addPrice _).expects(40).returns(40).once
// tests
it should "set price" in {
  assert(m.addPrice(40) == 40)
}

Solution

  • Let's explain the above code. The inAnyOrder section, defines assertions, for all tests. That means, that in each and every test in the suite, you should call exactly once to:

    1. m.addPrice(40)
    2. m.addStock(20)
    3. m.addStock(10)

    while 2 must come before 3. Therefore, each test fails due to lack of 2 calls. For example, the test:

    it should "add new qty. Step 1" in {
      assert(m.addStock(20) == 20)
    }
    

    fails with the message:

    Expected:
    inAnyOrder {
    inAnyOrder {
        <mock-1> Product.addPrice(40) once (never called - UNSATISFIED)
        inSequence {
        <mock-1> Product.addStock(20) once (called once)
        <mock-1> Product.addStock(10) once (never called - UNSATISFIED)
        }
    }
    }
    
    Actual:
    <mock-1> Product.addStock(20)
    ScalaTestFailureLocation: ProductSpec at (ProductSpec.scala:20)
    org.scalatest.exceptions.TestFailedException: Unsatisfied expectation:
    

    Because 1 and 3 were not satisfied.

    If you'll try a test like:

    it should "set price" in {
      assert(m.addPrice(40) == 40)
      assert(m.addStock(20) == 20)
      assert(m.addStock(10) == 30)
    }
    

    it will pass. Please note that if you change the order of 2 & 3, the same test will fail.