File: duck_types.feature

package info (click to toggle)
ruby-bogus 0.1.7-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 844 kB
  • sloc: ruby: 4,237; makefile: 8; sh: 2
file content (144 lines) | stat: -rw-r--r-- 4,005 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
Feature: Faking duck types

  Sometimes, your system contains more than one class that can satisfy a given role. Which of the possible implementations do you pick then, and how can you make sure that their interfaces stay in sync?

  Bogus gives you a way of extracting the "lowest common interface" out of multiple classes. All you need to do is just pass the classes in question to the `make_duck` method:

      make_duck(DatabaseLogger, NetworkLogger, DevNullLogger)

  This call will return a new class, that contains only the methods that exist in the public interface of all of the given classes, and in all of them have the same signature.

  To make things easier for you, Bogus will automatically make a duck type for you, every time you return multiple classes from fake:

      fake(:logger) { [DatabaseLogger, NetworkLogger, DevNullLogger] }

  It is of course also possible to do the same thing in the global fake configuration:

      Bogus.fakes do
        fake :logger, class: proc{ 
          [DatabaseLogger, NetworkLogger, DevNullLogger]
        }
      end

  Background:
    Given a file named "loggers.rb" with:
    """ruby
    class DatabaseLogger
      def error(message); end
      def warn(message, level); end
      def connection; end
    end

    class NetworkLogger
      def error(message); end
      def warn(message, level); end
      def socket; end
    end

    class DevNullLogger
      def error(message); end
      def warn(message, level); end
      def debug(message); end
    end

    class ExceptionNotifier
      def initialize(logger)
        @logger = logger
      end

      def notify(exception)
        @logger.error(exception)
      end
    end

    """

  Scenario: Copying instance methods
    Then spec file with following content should pass:
    """ruby
    require_relative 'loggers'

    describe "make_duck" do
      let(:duck) { make_duck(DatabaseLogger, NetworkLogger, 
                            DevNullLogger) }
      let(:duck_instance) { duck.new }

      it "responds to error" do
        expect(duck_instance).to respond_to(:error)
      end

      it "has arity 1 for error" do
        expect(duck_instance.method(:error).arity).to eq(1)
      end

      it "responds to warn" do
        expect(duck_instance).to respond_to(:warn)
      end

      it "has arity 2 for warn" do
        expect(duck_instance.method(:warn).arity).to eq(2)
      end

      it "does not respond to connection" do
        expect(duck_instance).not_to respond_to(:connection)
      end

      it "does not respond to socket" do
        expect(duck_instance).not_to respond_to(:socket)
      end

      it "does not respond to debug" do
        expect(duck_instance).not_to respond_to(:debug)
      end
    end
    """

  Scenario: Faking duck types
    Then spec file with following content should pass:
    """ruby
    require_relative 'loggers'

    describe "fake with multiple classes" do
      fake(:logger) { [DatabaseLogger, 
                      NetworkLogger, 
                      DevNullLogger] }

      let(:notifier) { ExceptionNotifier.new(logger) }

      it "logs the exception" do
        notifier.notify("whoa!")

        expect(logger).to have_received.error("whoa!")
      end
    end
    """

  Scenario: Globally configured duck types
    Given a file named "fakes.rb" with:
    """ruby
    Bogus.fakes do
      logger_implementations = proc{ [DatabaseLogger, 
                                      NetworkLogger, 
                                      DevNullLogger] }

      fake :logger, class: logger_implementations
    end
    """

    Then spec file with following content should pass:
    """ruby
    require_relative 'fakes'
    require_relative 'loggers'

    describe "fake with multiple classes" do
      fake(:logger)

      let(:notifier) { ExceptionNotifier.new(logger) }

      it "logs the exception" do
        notifier.notify("whoa!")

        expect(logger).to have_received.error("whoa!")
      end
    end
    """