At the end of Part 1 I had a working Employee class.

On with the main logic…

TestCase subclass: BirthdayServiceTestCase [
    <comment: nil>
    <category: 'BirthdayKata-Tests'>

    testNoEmployeesWithBirthday [
        <category: 'tests'>
        | repository mailSender kata |
        repository := InMemoryRepository new.
        mailSender := MockEmailSender new.
        kata := BirthdayService new repository: repository; mailSender: mailSender.

        kata sendGreetings: (Date newDay: 14 month: #Feb year: 2019).

        self assert: (mailSender count) equals: 0.
    ]
]

Nice new shiny test, I want to provide the repository and SMTP sender, and execute the code:

$ gst-sunit -v -f BirthdayKataTest.st BirthdayServiceTestCase
Loading BirthdayKataTest.st
BirthdayServiceTestCase>>##testNoEmployeesWithBirthday did not understand #new
TestVerboseLog>>logError: (SUnit.star#VFS.ZipFile/SUnit.st:608)
BirthdayServiceTestCase(TestCase)>>logError: (SUnit.star#VFS.ZipFile/SUnit.st:870)

And of course, it goes Boom!

There are at least three classes I need to define.

  • InMemoryRepository needs to provide employees on-demand.
  • MockEmailSender needs to accept emails for delivery, I added a count method to allow me to find out how many emails it has sent.
  • BirthdayService is the main driver, it uses the repository and email sender, to send emails to employees when it’s their birthday.
Object subclass: InMemoryRepository [
    <comment: 'I am just a test repository, I provide employees on demand.'>
    <category: 'BirthdayKata-Tests'>
]

Object subclass: MockEmailSender [
    <comment: 'I am just a mock email sender, I record emails I should send.'>
    <category: 'BirthdayKata-Tests'>

    count [
      "I return the number of emails sent."
      ^0
    ]
]

These are simplest possible versions that I can write to allow me to get that test passing.

Finally, my empty implementation of the BirthdayService driver.

Object subclass: BirthdayService [
    <comment: 'I am the main driver for the BirthdayKata.'>
    <category: 'BirthdayKata'>
    | employeeRepository mailSender |

    employeeRepository: anEmployeeRepository [
      "Set the value of the receiver's 'employeeRepository' instance variable to
       the argument anEmployeeRepository."
      <category: 'accessing'>

      employeeRepository := anEmployeeRepository.
    ]

    mailSender: aMailSender [
      "Set the value of the receiver's 'mailSender' instance variable to the
       argument aMailSender."
      <category: 'accessing'>

      mailSender := aMailSender.
    ]

    sendGreetings: aDate [
      "Send emails to all the employees whose birthday is on aDate."
      <category: 'accessing'>
    ]
]

Executing the tests…at the moment, I’m keeping my BirthdayService class in the test file for ease of writing.

$ gst-sunit -v -f BirthdayKataTest.st BirthdayServiceTestCase
Loading BirthdayKataTest.st
BirthdayServiceTestCase>>##testNoEmployeesWithBirthday .
1 run, 1 passes

Ok, that doesn’t say much…

Implementing basic functionality.

At this point, I got going with a simple implementation of my logic.

I’m not going to reproduce the entire code but some parts are worth illustrating:

This is post-initialization logic.

    InMemoryRepository class >> new [
      "Answer a new instance of the receiver."
      <category: 'basic'>
      ^self basicNew initialize.
    ]

    initialize [
      "Initialize the receiver's state"
      <category: 'private-initialization'>
      employees := Set new.
    ]

To store the set of dummy employees for the InMemoryRepository, I added a Set to store them in, to initialize the set this is the code, there’s no special method for initialization, and there are a variety of ways of doing this, with initialize being the common name for this.

Notice that new is a class method, and it delegates to basicNew before calling my initialize method with the result of basicNew as the receiver.

UPDATE: I see that the unstable release (from nearly 4 years ago) of GNU Smalltalk has fixed this issue http://smalltalk.gnu.org/news/gnu-smalltalk-3-2-91 The >>#initialize function will now be called when creating an object.

This is very similar to Ruby’s initialize mechanism (or constructors in pretty much every other OO language).

I added the following tests:

  • testNoEmployeesWithBirthday
  • testOneEmployeeWithBirthday
  • testOneEmployeeWithNonMatchingBirthday
  • testMultipleEmployeesWithMatchingBirthday

I don’t need to reproduce them all here but they look fairly similar to this.

testOneEmployeeWithBirthday [
    <category: 'tests'>
    | repository mailSender kata |
    repository := InMemoryRepository new.
    repository addEmployee: (Employee new birthDate: (Date newDay: 14 month: #Feb year: 1990)).
    mailSender := MockEmailSender new.
    kata := BirthdayService new employeeRepository: repository; mailSender: mailSender.

    kata sendGreetings: (Date newDay: 14 month: #Feb year: 2019).

    self assert: (mailSender count) equals: 1.
]

I added a mechanism for adding employees to that Set that was added, this simply delegates to add: on the employees set.

And in the MockEmailSender I added a count variable.

sendEmail: anEmail [
  "Send an email to an employee."
  <category: 'sending-email'>
  count := count + 1
]

initialize [
  "Initialize the receiver's state"
  <category: 'private-initialization'>
  count := 0
]

This is the simplest thing that works, all it does is increment the number of emails that have been sent, but before we can do much more, I need to revisit my Employee class, and add some new properties, and do a tiny bit of refactoring.

Finally…the core business logic…

sendGreetings: aDate [
  "Send emails to all the employees whose birthday is on aDate."
  <category: 'greetings'>
  (self employeesWithBirthday: aDate)
    do: [:each | mailSender sendEmail: 'testing']
]

employeesWithBirthday: aDate [
  "Answer the employees whose birthday it is."
  <category: 'private'>
  ^employeeRepository
    listEmployees select: [:each | each isBirthday: aDate]
]

employeesWithBirthday: returns a filtered set of employees, and sendGreetings: gets the filtered set, and delegates to the mailSender.

Small bit of refactoring

testNoEmployeesWithBirthday [
    <category: 'tests'>
    | repository mailSender kata |
    repository := InMemoryRepository new.

I was instantiating a new InMemoryRepository and MockEmailSender in each test, but, this is sUnit, so…

setUp [
  <category: 'setup'>
  super setUp.
  repository := InMemoryRepository new.
  mailSender := MockSMTPSender new.
]

By overriding setUp I can easily reduce the repetition.

So, in the next part, back to the Employee class, employees are more than just a birthDate y’know.