-2
object slack extends Logging{

  def sendSlackMessage(
    channel: String,
    message: String,
    webhook: String
  ):Boolean = {

    if(StringUtils.isNoneEmpty(channel, message, webhook)) {

      val api = new SlackApi(webhook)

      val slackMessage = new SlackMessage()
      slackMessage.setChannel(channel)
      slackMessage.setText(message)

      val results: Try[Unit] = Try(api.call(slackMessage))
      results match {
        case Failure(exception) =>
          logError("Invalid channel/webhook. Couldn't sent notification!", exception)
          false
        case Success(value) =>
          true
      }
    }

    else {
      logError("Invalid parameters. Couldn't sent notification!")
      false
    }
  }
}

I would like to know if it's possible to unit test sendSlackMessage(). The challenge I'm facing right now is that I cannot figure out a way to mock new SlackApi() and even if I mock, I don't know how to tell the function to use the mocked value to instantiate and make the api call.

dirkk
  • 6,160
  • 5
  • 33
  • 51
Shabir Hamid
  • 148
  • 1
  • 8
  • Does this answer your question? [Mock a constructor with parameter](https://stackoverflow.com/questions/13364406/mock-a-constructor-with-parameter) – talex Jul 08 '20 at 09:47
  • @talex Thanks for the sharing. But I don't like the fact that I need to adjust my source code to make it more mockito friendly. Also, I'm seeing if there is any solution without using additional dependencies (PowerMockito) – Shabir Hamid Jul 08 '20 at 10:08
  • You don't need to adjust your code to use PowerMockito. Unfortunately there is no way to avoid usage of extra libs, unless you want to implement them yourself :) – talex Jul 08 '20 at 10:18
  • 1
    @ShabirHamid adjusting your production code is one of the principles of the test driven design. It actually improves modularity of your code. Additionally, if you build this for 3rd party users, it's hard to assume all the cases they are going to use your code for, so providing customisations via additional parameters is a good idea. – Ivan Stanislavciuc Jul 08 '20 at 10:24
  • @talex It's because in another answer I read that PowerMockito can mock classes but not objects. If you can confirm that I'd be able to do it with my object `slack` without converting it into a class, then I can move in that direction. Otherwise, I'll proceed with Ivan's first approach of adding an extra parameter. – Shabir Hamid Jul 08 '20 at 10:25
  • For JVM everything is class. And you not mocking `slack`, you testing it. So it is possible to test your class without modifying. Though PowerMockito is pretty magical, so if you can avoid using it (with extra parameter or in some other way) I suggest you not to use PowerMockito. – talex Jul 08 '20 at 11:28
  • I really love how developers enjoy writing bad code and then expect using bad techniques like reflection-based mocks to _"test"_ their code. - I would recommend you to follow SOLID principles, use abstractions to define the behaviors you want to expose and pass your dependencies on the constructors of your classes. Your code would be easier to test and refactor. – Luis Miguel Mejía Suárez Jul 08 '20 at 13:04

1 Answers1

1

You need to be able to "inject" instance of new SlackApi into your method.

you can do it in multiple ways. For example one of the following two

  1. Add new method parameter.
def sendSlackMessage(
    channel: String,
    message: String,
    webhook: String,
    api: SlackApi
  ):Boolean
  1. Make object slack be a class Slack(api: SlackApi)

In both cases, you can provided a mock instance of mock[SlackApi] and control expectations of the method call.

Ivan Stanislavciuc
  • 7,140
  • 15
  • 18
  • I would not want to use approach 1 as it would require the end user to send one more parameter. Looking forward to approach 2, if I can implement it using a `Trait` instead of a `class` – Shabir Hamid Jul 08 '20 at 09:59