Spock Testing – Soup to Nuts



Spock Testing – Soup to Nuts

1 0


spock-talk

Spock Talk

On Github zanthrash / spock-talk

Spock Testing

Soup to Nuts

Zan Thrash

Object Partners, Inc

  • zan.thrash@objectpartners.com
  • @zanthrash
  • +zanthrash
  • zanthrash

What Is Spock?

Spock is a testing and specification framework for Java and Groovy applications. What makes it stand out from the crowd is its beautiful and highly expressive specification language .
~ Peter Niederwieser Spock Author

What Is Spock?

JUnit

RSpec

Cucumber

jMock

Why Spock?

Grails 2.3.x

Readability

Parametrization

Crazy Awesome Mocking

Extensible

  • In a world where we already have JUnit & Groovy/Grails specific testing & dozens of mocking frameworks to choose from....

Some Terminology

Specification

                        
    class OrderServiceSpec extends Specification {
        ...
    }
                        
                    
any Java or Groovy class that extends Specification

Fixture Methods

                        
    class OrderServiceSpec extends Specification {

        def setup() { ... }
        def cleanup() { ... }
        def setupSpec() { ... }
        def cleanupSpec() { ... }

    }
                        
                    

Feature Methods

                        
    class OrderServiceSpec extends Specification {

        def "this is an actual test"() {
            ...
        }

    }
                        
                    

Blocks

                        
    class OrderServiceSpec extends Specification {

        def "this is an actual test"() {
            setup:
            given:
            when:
            then:
            expect:
            where:
            cleanup:
            and:
        }

    }
                        
                    

Convert

@TestFor(OrderService)
class OrderServiceTests {

    def mockInventoryService      
    def mockShippingCostService   

    @Before
    void setup() {
        mockInventoryService = mockFor(InventoryService) 
        service.inventoryService = mockInventoryService.createMock()   
        mockShippingCalculatorService = mockFor(ShippingCalculatorService)  
        service.shippingCostService = mockShippingCostService.createMock()  
    }

    @Test  
    void calculateOrderForOneBeer() {  
        mockInventoryService.demand.removeFromStock(2) { a -> }   
        mockShippingCostService.demand.calculate(1) { Order order -> 1.00} 

        Item beer = new Item(name: 'Beer', price: 2.50)             
        OrderItem orderItem = new OrderItem(item: beer, quantity:1) 
        Order order = new Order() 
        order.addItem(orderItem)  

        BigDecimal total = service.checkout(order)  

        assert total == 3.50      
        assertEquals(total, 3.50) 

        mockInventoryService.verify() 
    }
}

                    

Vanilla Test

@TestFor(OrderService)
class OrderServiceTests extends Specification{

    def mockInventoryService
    def mockShippingCostService

    @Before
    void setup() {
        mockInventoryService = mockFor(InventoryService)
        service.inventoryService = mockInventoryService.createMock()
        mockShippingCalculatorService = mockFor(ShippingCalculatorService)
        service.shippingCostService = mockShippingCostService.createMock()
    }

    @Test
    void calculateOrderForOneBeer() {
        mockInventoryService.demand.removeFromStock(2) { a -> }
        mockShippingCostService.demand.calculate(1) { Order order -> 1.00}

        Item beer = new Item(name: 'Beer', price: 2.50)
        OrderItem orderItem = new OrderItem(item: beer, quantity:1)
        Order order = new Order()
        order.addItem(orderItem)

        BigDecimal total = service.checkout(order)

        assert total == 3.50
        assertEquals(total, 3.50)

        mockInventoryService.verify()
    }
}

                    

Step 1: Extend Specification

@TestFor(OrderService)
class OrderServiceSpec extends Specification{

    def mockInventoryService
    def mockShippingCostService

    @Before
    void setup() {
        mockInventoryService = mockFor(InventoryService)
        service.inventoryService = mockInventoryService.createMock()
        mockShippingCalculatorService = mockFor(ShippingCalculatorService)
        service.shippingCostService = mockShippingCostService.createMock()
    }

    @Test
    void calculateOrderForOneBeer() {
        mockInventoryService.demand.removeFromStock(2) { a -> }
        mockShippingCostService.demand.calculate(1) { Order order -> 1.00}

        Item beer = new Item(name: 'Beer', price: 2.50)
        OrderItem orderItem = new OrderItem(item: beer, quantity:1)
        Order order = new Order()
        order.addItem(orderItem)

        BigDecimal total = service.checkout(order)

        assert total == 3.50
        assertEquals(total, 3.50)

        mockInventoryService.verify()
    }
}

                    

Step 2: Change class name

@TestFor(OrderService)
class OrderServiceSpec extends Specification{

    def mockInventoryService
    def mockShippingCostService

    @Before
    void setup() {
        mockInventoryService = mockFor(InventoryService)
        service.inventoryService = mockInventoryService.createMock()
        mockShippingCalculatorService = mockFor(ShippingCalculatorService)
        service.shippingCostService = mockShippingCostService.createMock()
    }

    @Test
    void "calculate order for one beer"() {
        mockInventoryService.demand.removeFromStock(2) { a -> }
        mockShippingCostService.demand.calculate(1) { Order order -> 1.00}

        Item beer = new Item(name: 'Beer', price: 2.50)
        OrderItem orderItem = new OrderItem(item: beer, quantity:1)
        Order order = new Order()
        order.addItem(orderItem)

        BigDecimal total = service.checkout(order)

        assert total == 3.50
        assertEquals(total, 3.50)

        mockInventoryService.verify()
    }
}

                    

Step 3: humanize test name

@TestFor(OrderService)
class OrderServiceSpec extends Specification{

    def mockInventoryService
    def mockShippingCostService

    @Before
    void setup() {
        mockInventoryService = mockFor(InventoryService)
        service.inventoryService = mockInventoryService.createMock()
        mockShippingCalculatorService = mockFor(ShippingCalculatorService)
        service.shippingCostService = mockShippingCostService.createMock()
    }

    @Test
    void "calculate order for one beer"() {
        given:
            mockInventoryService.demand.removeFromStock(2) { a -> }
            mockShippingCostService.demand.calculate(1) { Order order -> 1.00}

            Item beer = new Item(name: 'Beer', price: 2.50)
            OrderItem orderItem = new OrderItem(item: beer, quantity:1)
            Order order = new Order()
            order.addItem(orderItem)

        BigDecimal total = service.checkout(order)

        assert total == 3.50
        assertEquals(total, 3.50)

        mockInventoryService.verify()
    }
}

                    

Step 4: given block

@TestFor(OrderService)
class OrderServiceSpec extends Specification{

    def mockInventoryService
    def mockShippingCostService

    @Before
    void setup() {
        mockInventoryService = mockFor(InventoryService)
        service.inventoryService = mockInventoryService.createMock()
        mockShippingCalculatorService = mockFor(ShippingCalculatorService)
        service.shippingCostService = mockShippingCostService.createMock()
    }

    @Test
    void "calculate order for one beer"() {
        given:
            mockInventoryService.demand.removeFromStock(2) { a -> }
            mockShippingCostService.demand.calculate(1) { Order order -> 1.00}

            Item beer = new Item(name: 'Beer', price: 2.50)
            OrderItem orderItem = new OrderItem(item: beer, quantity:1)
            Order order = new Order()
            order.addItem(orderItem)

        when:
            BigDecimal total = service.checkout(order)

        assert total == 3.50
        assertEquals(total, 3.50)

        mockInventoryService.verify()
    }
}

                    

Step 5: when block

@TestFor(OrderService)
class OrderServiceSpec extends Specification{

    def mockInventoryService
    def mockShippingCostService

    @Before
    void setup() {
        mockInventoryService = mockFor(InventoryService)
        service.inventoryService = mockInventoryService.createMock()
        mockShippingCalculatorService = mockFor(ShippingCalculatorService)
        service.shippingCostService = mockShippingCostService.createMock()
    }

    @Test
    void "calculate order for one beer"() {
        given:
            mockInventoryService.demand.removeFromStock(2) { a -> }
            mockShippingCostService.demand.calculate(1) { Order order -> 1.00}

            Item beer = new Item(name: 'Beer', price: 2.50)
            OrderItem orderItem = new OrderItem(item: beer, quantity:1)
            Order order = new Order()
            order.addItem(orderItem)

        when:
            BigDecimal total = service.checkout(order)

        then:
            assert total == 3.50
            assertEquals(total, 3.50)
            mockInventoryService.verify()
    }
}

                    

Step 6: then block

@TestFor(OrderService)
class OrderServiceSpec extends Specification{

    def mockInventoryService
    def mockShippingCostService

    @Before
    void setup() {
        mockInventoryService = mockFor(InventoryService)
        service.inventoryService = mockInventoryService.createMock()
        mockShippingCalculatorService = mockFor(ShippingCalculatorService)
        service.shippingCostService = mockShippingCostService.createMock()
    }

    @Test
    void "calculate order for one beer"() {
        given:
            mockInventoryService.demand.removeFromStock(2) { a -> }
            mockShippingCostService.demand.calculate(1) { Order order -> 1.00}

            Item beer = new Item(name: 'Beer', price: 2.50)
            OrderItem orderItem = new OrderItem(item: beer, quantity:1)
            Order order = new Order()
            order.addItem(orderItem)

        when:
            BigDecimal total = service.checkout(order)

        then:
            assert total == 3.50
            assertEquals(total, 3.50)
            mockInventoryService.verify()
    }
}

                    

Step 6: then block

@TestFor(OrderService)
class OrderServiceSpec extends Specification{

    def mockInventoryService
    def mockShippingCostService

    @Before
    void setup() {
        mockInventoryService = mockFor(InventoryService)
        service.inventoryService = mockInventoryService.createMock()
        mockShippingCalculatorService = mockFor(ShippingCalculatorService)
        service.shippingCostService = mockShippingCostService.createMock()
    }

    @Test
    void "calculate order for one beer"() {
        given:
            mockInventoryService.demand.removeFromStock(2) { a -> }
            mockShippingCostService.demand.calculate(1) { Order order -> 1.00}

            Item beer = new Item(name: 'Beer', price: 2.50)
            OrderItem orderItem = new OrderItem(item: beer, quantity:1)
            Order order = new Order()
            order.addItem(orderItem)

        when:
            BigDecimal total = service.checkout(order)

        then:
            total == 3.50
            assertEquals(total, 3.50)
            mockInventoryService.verify() == null
    }
}

                    

Step 6: then block

@TestFor(OrderService)
class OrderServiceSpec extends Specification{

    def mockInventoryService
    def mockShippingCostService

    @Before
    void setup() {
        mockInventoryService = mockFor(InventoryService)
        service.inventoryService = mockInventoryService.createMock()
        mockShippingCalculatorService = mockFor(ShippingCalculatorService)
        service.shippingCostService = mockShippingCostService.createMock()
    }

    @Test
    void "calculate order for one beer"() {
        given:
            mockInventoryService.demand.removeFromStock(2) { a -> }
            mockShippingCostService.demand.calculate(1) { Order order -> 1.00}

            Item beer = new Item(name: 'Beer', price: 2.50)
            OrderItem orderItem = new OrderItem(item: beer, quantity:1)
            Order order = new Order()
            order.addItem(orderItem)

        when:
            BigDecimal total = service.checkout(order)
            mockInventoryService.verify()

        then:
            total == 3.50
            assertEquals(total, 3.50)
    }
}

                    

Step 6: then block

@TestFor(OrderService)
class OrderServiceSpec extends Specification{

    def mockInventoryService
    def mockShippingCostService

    @Before
    void setup() {
        mockInventoryService = mockFor(InventoryService)
        service.inventoryService = mockInventoryService.createMock()
        mockShippingCalculatorService = mockFor(ShippingCalculatorService)
        service.shippingCostService = mockShippingCostService.createMock()
    }

    @Test
    void "calculate order for one beer"() {
        given: "Setup dependencies and create valid order"
            mockInventoryService.demand.removeFromStock(2) { a -> }
            mockShippingCostService.demand.calculate(1) { Order order -> 1.00}

            Item beer = new Item(name: 'Beer', price: 2.50)
            OrderItem orderItem = new OrderItem(item: beer, quantity:1)
            Order order = new Order()
            order.addItem(orderItem)

        when: "checkout the order"
            BigDecimal total = service.checkout(order)
            mockInventoryService.verify()

        then: "total should be = price * quantity + shipping cost"
            total == 3.50
    }
}

                    

Step 7: document blocs

@TestFor(OrderService)
class OrderServiceSpec extends Specification{

    def mockInventoryService
    def mockShippingCostService

    @Before
    void setup() {
        mockInventoryService = mockFor(InventoryService)
        service.inventoryService = mockInventoryService.createMock()
        mockShippingCalculatorService = mockFor(ShippingCalculatorService)
        service.shippingCostService = mockShippingCostService.createMock()
    }

    @Test
    void "calculate order for one beer"() {
        given: "Setup dependencies and create valid order"
            mockInventoryService.demand.removeFromStock(2) { a -> }
            mockShippingCostService.demand.calculate(1) { Order order -> 1.00}

            Item beer = new Item(name: 'Beer', price: 2.50)
            OrderItem orderItem = new OrderItem(item: beer, quantity:1)
            Order order = new Order()
            order.addItem(orderItem)

        when: "checkout the order"
            BigDecimal total = service.checkout(order)
            mockInventoryService.verify()

        then: "total should be = price * quantity + shipping cost"
             total == 3.50
    }
}

                    

Step 8: 'and' blocks

@TestFor(OrderService)
class OrderServiceSpec extends Specification{

    def mockInventoryService
    def mockShippingCostService

    @Before
    void setup() {
        mockInventoryService = mockFor(InventoryService)
        service.inventoryService = mockInventoryService.createMock()
        mockShippingCalculatorService = mockFor(ShippingCalculatorService)
        service.shippingCostService = mockShippingCostService.createMock()
    }

    @Test
    void "calculate order for one beer"() {
        given: "Setup dependencies"
            mockInventoryService.demand.removeFromStock(2) { a -> }
            mockShippingCostService.demand.calculate(1) { Order order -> 1.00}

        and: "Create valid order"
            Item beer = new Item(name: 'Beer', price: 2.50)
            OrderItem orderItem = new OrderItem(item: beer, quantity:1)
            Order order = new Order()
            order.addItem(orderItem)

        when: "checkout the order"
            BigDecimal total = service.checkout(order)
            mockInventoryService.verify()

        then: "total should be = price * quantity + shipping cost"
             total == 3.50
    }
}

                    

Step 8: 'and' blocks

Expect Block

    void "add 2 things together" () {
        expect:
            1 + 1 = 2
            'Bat' + 'man' == 'Bat man'
            99 + 1 == 100
    }
                    

Expect Block

    --Output from test adding things--
    | Failure:  test adding things
    |  Condition not satisfied:

    'Bat' + 'man' == 'Bat man'
    |             |
    Batman        false
    1 difference (85% similarity)
    Bat(-)man
    Bat( )man

                    

Parameterized Testing

Parameterized Testing

        void "adding things"() {
            expect:
                a + b == c

            where:
                a << [1, 'Bat', 100]
                b << [1, 'man', 1]
                c << [2, 'Bat man', 101]
        }
    
    

Parameterized Testing

Parameterized Testing: Unroll

        @Unroll  
        void "adding things"() {
            expect:
                a + b == c

            where:
                a << [1, 'Bat', 100]
                b << [1, 'man', 1]
                c << [2, 'Bat man', 101]
        }
    
    

Parameterized Testing: Basic Unroll

Parameterized Testing: Advanced Unroll

        @Unroll("#a + #b = #c")  
        void "adding things"() {
            expect:
                a + b == c

            where:
                a << [1, 'Bat', 100]
                b << [1, 'man', 1]
                c << [2, 'Bat man', 101]
        }
    
    

Parameterized Testing: Advanced Unroll

        @Unroll
        void "#a + #b = #c"() { 
            expect:
                a + b == c

            where:
                a << [1, 'Bat', 100]
                b << [1, 'man', 1]
                c << [2, 'Bat man', 101]
        }
    
    

Parameterized Testing: Advanced Unroll

Parameterized Testing: Tables

        @Unroll
        void "#a + #b = #c"() {
            expect:
                a + b == c

            where:
                a << [1, 'Bat', 100]
                b << [1, 'man', 1]
                c << [2, 'Bat man', 101]
        }
    
    

Parameterized Testing: Tables

        @Unroll
        void "#a + #b = #c"() {
            expect:
                a + b == c

            where:
                a << [1, 'Bat', 100, "Foo"]
                b << [1, 'man', 1, 1]
                c << [2, 'Bat man', 101, Foo1]
        }
    
    

Parameterized Testing: Tables

        @Unroll
        void "#a + #b = #c"() {
            expect:
                a + b == c

            where:
                a << [1, 'Bat', 100, "Foo", "2"]
                b << [1, 'man', 1, 1, "99"]
                c << [2, 'Bat man', 101, Foo1, "299"]
        }
    
    

Parameterized Testing: Tables

        @Unroll
        void "#a + #b = #c"() {
            expect:
                a + b == c

            where:
                a     | b     | c    variables
                1     | 1     | 2          
                'Bat' | 'man' | 'Bat man'    Tests
                100   | 1     | 101        
        }
    
    

Parameterized Testing: Tables

        @Unroll
        void "#a + #b = #c"() {
            expect:
                a + b == c

            where:
                a     | b     || c
                1     | 1     || 2
                'Bat' | 'man' || 'Bat man'
                100   | 1     || 101
        }
    
    

Testing Exceptions

    void "should throw IllegalArgumentException"() {
        when:
            service.doFoo('bad param')

        then:
            thrown(IllegalArgumentException)

    }
    
    

Interactions

@TestFor(OrderService)
class OrderServiceSpec extends Specification{

    def mockInventoryService      
    def mockShippingCostService  

    @Before
    void setup() {
        mockInventoryService = mockFor(InventoryService)                   
        service.inventoryService = mockInventoryService.createMock()       
        mockShippingCostService = mockFor(ShippingCalculatorService)       
        service.shippingCostService = mockShippingCostService.createMock() 
    }

    @Test
    void "calculate order for one beer"() {
        given: "Setup dependencies"
            mockInventoryService.demand.removeFromStock(2) { a -> }           
            mockShippingCostService.demand.calculate(1) { Order order -> 1.00}

        and: "Create valid order"
            Item beer = new Item(name: 'Beer', price: 2.50)
            OrderItem orderItem = new OrderItem(item: beer, quantity:1)
            Order order = new Order()
            order.addItem(orderItem)

        when: "checkout the order"
            BigDecimal total = service.checkout(order)
            mockInventoryService.verify()   

        then: "total should be = price * quantity + shipping cost"
             total == 3.50
    }
}

                    
  • Original Test Case
@TestFor(OrderService)
class OrderServiceSpec extends Specification{

    InventoryService mockInventoryService
    InventoryService mockShippingCostService

    @Before
    void setup() {
        mockInventoryService = mockFor(InventoryService)
        service.inventoryService = mockInventoryService.createMock()
        mockShippingCalculatorService = mockFor(ShippingCalculatorService)
        service.shippingCostService = mockShippingCostService.createMock()
    }

    @Test
    void "calculate order for one beer"() {
        given: "Setup dependencies"
            mockInventoryService.demand.removeFromStock(2) { a -> }
            mockShippingCostService.demand.calculate(1) { Order order -> 1.00}

        and: "Create valid order"
            Item beer = new Item(name: 'Beer', price: 2.50)
            OrderItem orderItem = new OrderItem(item: beer, quantity:1)
            Order order = new Order()
            order.addItem(orderItem)

        when: "checkout the order"
            BigDecimal total = service.checkout(order)
            mockInventoryService.verify()

        then: "total should be = price * quantity + shipping cost"
             total == 3.50
    }
}

                    
  • Statically Type collaborators
@TestFor(OrderService)
class OrderServiceSpec extends Specification{

    InventoryService mockInventoryService
    InventoryService mockShippingCostService

    @Before
    void setup() {
        mockInventoryService = Mock()
        service.inventoryService = mockInventoryService.createMock()
        mockShippingCalculatorService = Mock()
        service.shippingCostService = mockShippingCostService.createMock()
    }

    @Test
    void "calculate order for one beer"() {
        given: "Setup dependencies"
            mockInventoryService.demand.removeFromStock(2) { a -> }
            mockShippingCostService.demand.calculate(1) { Order order -> 1.00}

        and: "Create valid order"
            Item beer = new Item(name: 'Beer', price: 2.50)
            OrderItem orderItem = new OrderItem(item: beer, quantity:1)
            Order order = new Order()
            order.addItem(orderItem)

        when: "checkout the order"
            BigDecimal total = service.checkout(order)
            mockInventoryService.verify()

        then: "total should be = price * quantity + shipping cost"
             total == 3.50
    }
}

                    
  • Creat the mock instances by calling the Mock() method
@TestFor(OrderService)
class OrderServiceSpec extends Specification{

    InventoryService mockInventoryService
    InventoryService mockShippingCostService

    @Before
    void setup() {
        mockInventoryService = Mock()
        service.inventoryService = mockInventoryService.createMock()
        mockShippingCalculatorService = Mock()
        service.shippingCostService = mockShippingCostService.createMock()
    }

    @Test
    void "calculate order for one beer"() {
        given: "Setup dependencies"
            mockInventoryService.demand.removeFromStock(2) { a -> }
            mockShippingCostService.demand.calculate(1) { Order order -> 1.00}

        and: "Create valid order"
            Item beer = new Item(name: 'Beer', price: 2.50)
            OrderItem orderItem = new OrderItem(item: beer, quantity:1)
            Order order = new Order()
            order.addItem(orderItem)

        when: "checkout the order"
            BigDecimal total = service.checkout(order)
            mockInventoryService.verify()

        then: "total should be = price * quantity + shipping cost"
             total == 3.50
    }
}

                    
  • Don't need to call createMock anymore
@TestFor(OrderService)
class OrderServiceSpec extends Specification{

    InventoryService mockInventoryService
    InventoryService mockShippingCostService

    @Before
    void setup() {
        mockInventoryService = Mock()
        service.inventoryService = mockInventoryService
        mockShippingCalculatorService = Mock()
        service.shippingCostService = mockShippingCostService
    }

    @Test
    void "calculate order for one beer"() {
        given: "Setup dependencies"
            mockInventoryService.demand.removeFromStock(2) { a -> }
            mockShippingCostService.demand.calculate(1) { Order order -> 1.00}

        and: "Create valid order"
            Item beer = new Item(name: 'Beer', price: 2.50)
            OrderItem orderItem = new OrderItem(item: beer, quantity:1)
            Order order = new Order()
            order.addItem(orderItem)

        when: "checkout the order"
            BigDecimal total = service.checkout(order)
            mockInventoryService.verify()

        then: "total should be = price * quantity + shipping cost"
            total == 3.50
    }
}

                    
  • Remvoe the demand
@TestFor(OrderService)
class OrderServiceSpec extends Specification{

    InventoryService mockInventoryService
    InventoryService mockShippingCostService

    @Before
    void setup() {
        mockInventoryService = Mock()
        service.inventoryService = mockInventoryService
        mockShippingCalculatorService = Mock()
        service.shippingCostService = mockShippingCostService
    }

    @Test
    void "calculate order for one beer"() {
        given: "Setup dependencies"
            mockInventoryService.removeFromStock(2) { a -> }
            mockShippingCostService.calculate(1) { Order order -> 1.00}

        and: "Create valid order"
            Item beer = new Item(name: 'Beer', price: 2.50)
            OrderItem orderItem = new OrderItem(item: beer, quantity:1)
            Order order = new Order()
            order.addItem(orderItem)

        when: "checkout the order"
            BigDecimal total = service.checkout(order)
            mockInventoryService.verify()

        then: "total should be = price * quantity + shipping cost"
             total == 3.50
    }
}

                    
  • Move the location cardinality from here ...
@TestFor(OrderService)
class OrderServiceSpec extends Specification{

    InventoryService mockInventoryService
    InventoryService mockShippingCostService

    @Before
    void setup() {
        mockInventoryService = Mock()
        service.inventoryService = mockInventoryService
        mockShippingCalculatorService = Mock()
        service.shippingCostService = mockShippingCostService
    }

    @Test
    void "calculate order for one beer"() {
        given: "Setup dependencies"
            2 * mockInventoryService.removeFromStock() { a -> }
            1 * mockShippingCostService.calculate() { Order order -> 1.00}

        and: "Create valid order"
            Item beer = new Item(name: 'Beer', price: 2.50)
            OrderItem orderItem = new OrderItem(item: beer, quantity:1)
            Order order = new Order()
            order.addItem(orderItem)

        when: "checkout the order"
            BigDecimal total = service.checkout(order)
            mockInventoryService.verify()

        then: "total should be = price * quantity + shipping cost"
             total == 3.50
    }
}

                    
  • Move the cardinality to here
@TestFor(OrderService)
class OrderServiceSpec extends Specification{

    InventoryService mockInventoryService
    InventoryService mockShippingCostService

    @Before
    void setup() {
        mockInventoryService = Mock()
        service.inventoryService = mockInventoryService
        mockShippingCalculatorService = Mock()
        service.shippingCostService = mockShippingCostService
    }

    @Test
    void "calculate order for one beer"() {
        given: "Setup dependencies"
            2 * mockInventoryService.removeFromStock() { a -> }
            1 * mockShippingCostService.calculate(){ Order order -> 1.00}

        and: "Create valid order"
            Item beer = new Item(name: 'Beer', price: 2.50)
            OrderItem orderItem = new OrderItem(item: beer, quantity:1)
            Order order = new Order()
            order.addItem(orderItem)

        when: "checkout the order"
            BigDecimal total = service.checkout(order)
            mockInventoryService.verify()

        then: "total should be = price * quantity + shipping cost"
             total == 3.50
    }
}

                    
@TestFor(OrderService)
class OrderServiceSpec extends Specification{

    InventoryService mockInventoryService
    InventoryService mockShippingCostService

    @Before
    void setup() {
        mockInventoryService = Mock()
        service.inventoryService = mockInventoryService
        mockShippingCalculatorService = Mock()
        service.shippingCostService = mockShippingCostService
    }

    @Test
    void "calculate order for one beer"() {
        given: "Setup dependencies"
            2 * mockInventoryService.removeFromStock(_)
            1 * mockShippingCostService.calculate(){ Order order -> 1.00}

        and: "Create valid order"
            Item beer = new Item(name: 'Beer', price: 2.50)
            OrderItem orderItem = new OrderItem(item: beer, quantity:1)
            Order order = new Order()
            order.addItem(orderItem)

        when: "checkout the order"
            BigDecimal total = service.checkout(order)
            mockInventoryService.verify()

        then: "total should be = price * quantity + shipping cost"
             total == 3.50
    }
}

                    
@TestFor(OrderService)
class OrderServiceSpec extends Specification{

    InventoryService mockInventoryService
    InventoryService mockShippingCostService

    @Before
    void setup() {
        mockInventoryService = Mock()
        service.inventoryService = mockInventoryService
        mockShippingCalculatorService = Mock()
        service.shippingCostService = mockShippingCostService
    }

    @Test
    void "calculate order for one beer"() {
        given: "Setup dependencies"
            2 * mockInventoryService.removeFromStock(_)
            1 * mockShippingCostService.calculate(){ Order order -> 1.00}

        and: "Create valid order"
            Item beer = new Item(name: 'Beer', price: 2.50)
            OrderItem orderItem = new OrderItem(item: beer, quantity:1)
            Order order = new Order()
            order.addItem(orderItem)

        when: "checkout the order"
            BigDecimal total = service.checkout(order)
            mockInventoryService.verify()

        then: "total should be = price * quantity + shipping cost"
             total == 3.50
    }
}

                    
@TestFor(OrderService)
class OrderServiceSpec extends Specification{

    InventoryService mockInventoryService
    InventoryService mockShippingCostService

    @Before
    void setup() {
        mockInventoryService = Mock()
        service.inventoryService = mockInventoryService
        mockShippingCalculatorService = Mock()
        service.shippingCostService = mockShippingCostService
    }

    @Test
    void "calculate order for one beer"() {
        given: "Setup dependencies"
            2 * mockInventoryService.removeFromStock(_)
            1 * mockShippingCostService.calculate(_ as Order) >> 1.00

        and: "Create valid order"
            Item beer = new Item(name: 'Beer', price: 2.50)
            OrderItem orderItem = new OrderItem(item: beer, quantity:1)
            Order order = new Order()
            order.addItem(orderItem)

        when: "checkout the order"
            BigDecimal total = service.checkout(order)
            mockInventoryService.verify()

        then: "total should be = price * quantity + shipping cost"
            total == 3.50
    }
}

                    
  • mockInventoryService:
  • -- Don't Care about the value submitted so we use the underbar wildecard
  • -- Don't care about the mocking a return value
  • mockShippingCostService:
  • -- Care that an Order object is passed
  • -- and we want to control the return value
@TestFor(OrderService)
class OrderServiceSpec extends Specification{

    InventoryService mockInventoryService
    InventoryService mockShippingCostService

    @Before
    void setup() {
        mockInventoryService = Mock()
        service.inventoryService = mockInventoryService
        mockShippingCalculatorService = Mock()
        service.shippingCostService = mockShippingCostService
    }

    @Test
    void "calculate order for one beer"() {
        given: "Setup dependencies"
            1 * mockInventoryService.removeFromStock(_)
            1 * mockShippingCostService.calculate(_ as Order) >> 1.00

        and: "Create valid order"
            Item beer = new Item(name: 'Beer', price: 2.50)
            OrderItem orderItem = new OrderItem(item: beer, quantity:1)
            Order order = new Order()
            order.addItem(orderItem)

        when: "checkout the order"
            BigDecimal total = service.checkout(order)
            mockInventoryService.verify()

        then: "total should be = price * quantity + shipping cost"
            total == 3.50
    }
}

                    
@TestFor(OrderService)
class OrderServiceSpec extends Specification{

    InventoryService mockInventoryService
    InventoryService mockShippingCostService

    @Before
    void setup() {
        mockInventoryService = Mock()
        service.inventoryService = mockInventoryService
        mockShippingCalculatorService = Mock()
        service.shippingCostService = mockShippingCostService
    }

    @Test
    void "calculate order for one beer"() {
        given: "Setup dependencies"
            1 * mockInventoryService.removeFromStock(_)               global
            1 * mockShippingCostService.demand.calculate(_) >> 1.00 

        and: "Create valid order"
            Item beer = new Item(name: 'Beer', price: 2.50)
            OrderItem orderItem = new OrderItem(item: beer, quantity:1)
            Order order = new Order()
            order.addItem(orderItem)

        when: "checkout the order"
            BigDecimal total = service.checkout(order)

        then: "total should be = price * quantity + shipping cost"
            total == 3.50
    }
}

                    
  • declaring in the setup/given block make the interaction global to the feature
@TestFor(OrderService)
class OrderServiceSpec extends Specification{

    InventoryService mockInventoryService
    InventoryService mockShippingCostService

    @Before
    void setup() {
        mockInventoryService = Mock()
        service.inventoryService = mockInventoryService
        mockShippingCalculatorService = Mock()
        service.shippingCostService = mockShippingCostService
    }

    @Test
    void "calculate order for one beer"() {
        given: "Create valid order"
            Item beer = new Item(name: 'Beer', price: 2.50)
            OrderItem orderItem = new OrderItem(item: beer, quantity:1)
            Order order = new Order()
            order.addItem(orderItem)

        when: "checkout the order"
            BigDecimal total = service.checkout(order)

        then: "total should be = price * quantity + shipping cost"
            total == 3.50
            1 * mockInventoryService.removeFromStock(_)               local
            1 * mockShippingCostService.demand.calculate(_) >> 1.00 
    }
}

                    
  • declaring in the then block make the interaction local to the when bloc

Interactions

Mocks

Stubs

Spies

Interactions

lenient by default

Interactions

Mocks

A substitute for a collaborator of the SUT. One where we do not care what the return value of the mocked collaborator is, only that it was called by our SUT

Interactions

Stubs

A substitute for a collaborator of the SUT. One where we do care what the return value of the mocked collaborator is, and want to have control whats returned

Interactions

Spies

A wrapper around a collaborator of the SUT. One where we want to passthrough to an actual object but want to also track that it was called.

Interactions

Parts

1 * fooService.bar('baz') >> 'returnValue'

Interactions

Parts Cardinality

1 * fooService.bar('baz') >> 'returnValue'

Interactions

Parts Target Constraint

1 * fooService.bar('baz') >> 'returnValue'

Interactions

Parts Method Constraint

1 * fooService.bar('baz') >> 'returnValue'

Interactions

Parts Argument Constraints

1 * fooService.bar('baz') >> 'returnValue'

Interactions

Parts Stub Return Value

1 * fooService.bar('baz') >> 'returnValue'

Interactions

Cardinality Integer

1 * fooService.bar('baz') >> 'returnValue'

  • Exactly N number of calls

Interactions

Cardinality Integer

0 * fooService.bar('baz') >> 'returnValue'

  • Ensure never called

Interactions

Cardinality Ranges

( 1..3 ) * fooService.bar('baz') >> 'returnValue'

  • Range is Inclusive
  • At least 1 call
  • But no more than 3

Interactions

Cardinality Ranges

( 1.._ ) * fooService.bar('baz') >> 'returnValue'

  • _ = Wildcard
  • At least 1 call
  • Unlimited Upper Bound

Interactions

Cardinality Ranges

( _..3) * fooService.bar('baz') >> 'returnValue'

  • Constrain the max number of calls

Interactions

Cardinality Wildcard

_ * fooService.bar('baz') >> 'returnValue'

  • Any number of calls including zero
  • ( 0.._ )
  • ( _.._ )
  • Kind of pointless

Interactions

Cardinality Errors

  • TooManyInvocationsError
  • TooFewInvocationsError

Interactions

Target Constraints Explicit

1 * mockTarget.methodName('mockParam') >> 'returnValue'

Interactions

Target Constraints WildCard

1 * _.methodName('mockParam') >> 'returnValue'

Interactions

Method Constraints Explicit

1 * mockTarget.methodName('mockParam') >> 'returnValue'

  • Can use groovy property syntax:
  • fooService.getBar() == fooService.bar

Interactions

Method Constraints WildCard

1 * mockTarget._('mockParam') >> 'returnValue'

Interactions

Method Constraints RegEx

1 * mockTarget./m.*e/('mockParam') >> 'returnValue'

  • Any method that stars with m & ends with e

Interactions

Argument Constraints Explicit

1 * mockTarget.methodName("mockParam")

Interactions

Argument Constraints Explicit (Exclusive)

1 * mockTarget.methodName(!"mockParam")

Interactions

Argument Constraints Wildcard

1 * mockTarget.methodName( _ )

  • Any single value including null

Interactions

Argument Constraints Wildcard

1 * mockTarget.methodName( _, _ )

  • Multiple values including null

Interactions

Argument Constraints Wildcard List

1 * mockTarget.methodName( *_ )

  • Any argument list including empty list

Interactions

Argument Constraints not null

1 * mockTarget.methodName( !null )

  • Any non null argument

Interactions

Argument Constraints Typed Wildcard

1 * mockTarget.methodName( _ as String )

  • Any non null argument that is a String

Interactions

Argument Constraints Predicate Closure

1 * mockTarget.methodName( { it.size() > 3 } )

  • Closure
  • Returns something truthy

Interactions

Shortcuts Mock all Method Calls

1 * mockTarget._(*_)

1 * mockTarget._

Interactions

Shortcuts Any method, any target

1 * _._

1 * _

Interactions

Careful Don't get carried away

_.._ * _._(*_) >> _

  • Completely valid...
  • Completely useless...
  • Don't do this!!!

Interactions

Mocks Creation (Dynamic)

def mockFooService = Mock(FooService)

Interactions

Mocks Creation (Static)

FooService mockFooService = Mock()

Interactions

Mocks Creation (w/ Interactions)

FooService mockFooService = Mock {        1 * getBar(_) }

Interactions

Mocks Usage

2 * mockFooService.getBar() >> new Bar()

Mock() is swiss army knive of Interactions Could do mocking And Stubbing

Interactions

Stubs Creation (Dynamic)

def mockFooService = Stub(FooService)

Interactions

Stubs Creation (Static)

FooService mockFooService = Stub()

Interactions

Stubs Creation (w/ Interactions)

FooService mockFooService = Stub {        getBar(_) >> new Bar() }

Interactions

Stubs Usage

mockFooService.getBar() >> new Bar()

Stub() Only care what return value if called Don't really care if it is called

Interactions

Spies Creation

def mockFooService = Spy(FooService, constructorArgs: [2])

Implements an actual object Not just an interfase Pass constructor args

Interactions

Spies Usage

2 * mockFooService.getBar()

Spies ALL method calls passthrough to the actual method

Interactions

Spies Usage

2 * mockFooService.getBar() >> new Bar()

Can Stub return value

No longer passes through to actual object

Spies ALL method calls passthrough to the actual method

Interactions

Groovy Specific Mocks

GroovyMock
GroovyStub
GroovySpy
Spies ALL method calls passthrough to the actual method

Interactions

Groovy Specific Mocks Uses

Mocking All Instances

def anyFoo = GroovyMock(Foo, global: true)

Interactions

Groovy Specific Mocks Uses

Mocking Constructors

def anyFoo = GroovyMock(Foo, global: true)
1 * new Foo("myFoo")

Interactions

Groovy Specific Mocks Uses

Mocking Static Methods

def anyFoo = GroovyStub(Foo, global: true)
Foo.PI() >> 3.333

Extensions

@Unroll

@Ignore

                        
        @Ignore
        def "my test"() {
            ...
        }
                        
                    
                        

        @Ignore("Unstable test")
        def "my test"() {
            ...
        }
                        
                    
                        
        @Ignore
        class FooSpec extends Specification {
            ...
        }
                        
                    

@IgnoreRest

Method ONLY annotation
                        

        @IgnoreRest
        def "only run me"() {
            ...
        }
                        
                    
Method ONLY annotation

@IgnoreIf

                        

@IgnoreIf({System.getProperty('os.name').contains('X')})
def "osx no bueno"() {
    ...
}
                        
                    
                        

@IgnoreIf({System.getProperty('os.name').contains('X')})
class NoOSXSpec extends Specification() {
    ...
}
                        
                    

@Require

                        

@Require({System.getProperty('os.name').contains('X')})
def "only for OS X"() {
    ...
}
                        
                    

@Stepwise

                        

@Stepwise
class ExecuteInOrderSpec extends Specification() {

    def "I run first"() {
        ...
    }

    def "I run next"() {
        ...
    }
}
                        
                    

@Timeout(10)

@Timeout(value = 100, unit = TimeUnit.MILLISECONDS)

@Use

                        

class ListExtensions () {
    static avg(List list) { list.sum() / list.size() }
}



class MySpec extneds Specification() {

    @Use([ListExtensions])
    def "averaging contents of a list"() {
        expect:
            [1, 2, 3].avg() == 2
    }
}
                        
                    
                        

class ListExtensions () {
    static avg(List list) { list.sum() / list.size() }
}



class MySpec extneds Specification() {

    def "averaging contents of a list"() {
        expect:
            use(ListExtensions) {
                [1, 2, 3].avg() == 2
            }
    }
}
                        
                    

@ConfineMetaClassChange

                        

class MySpec extends Specification() {

    @ConfineMetaClassChanges([String])
    def "mucking with string"() {
        when:
            String.metaClass.toUpperCase = { "FOO${delegate}" }
        then:
            "Baz".toUpperCase() == "FOOBaz"
    }



    def "more string stuff"() {
        when:
            String results = "foo".toUpperCase()
        then:
            results == "FOO"
    }
}
                        
                    

@AutoCleanup

                        

    class MySpec extends Specification() {
        @AutoCleanup BufferedReader xmlFileReader
        @AutoCleanup('dispose') mockDB

        def startup() {
            xmlFile = new File('foo.xml').newReader()
            mockDB = new MockDB()
        }
    }
                        
                    

@Shared

                        

class MySpec extends Specification() {

    @Shared BufferedReader xmlFileReader =
                    new File(