4

Mocking The Right Way

 3 years ago
source link: https://blog.knoldus.com/mocking-the-right-way/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Mocking The Right Way

Reading Time: 3 minutes

Introduction to Mockito

Mockito is a Java-based mocking framework, used with other testing frameworks like JUnit and TestNG. While written for effective unit testing of Java applications, Mockito was not as Scala-friendly as a developer would like it to be. This created an opportunity to move from VanillaMockito to MockitoScala, mockito for scala.

Using Mockito, you don’t need to worry about any external dependency, like a connection to a  data source. For example, when we mock a method, we specify what to return when the method is invoked in the test environment, without executing the underlying logic.

But working with Mockito becomes a bit cumbersome when the layers of our application are not properly separated . Tight coupling between the layers makes it difficult to use Mockito or any other mocking framework for unit testing.

Designing the layers for easy mocking

While developing your application, decide on an architecture that ensures loose coupling between the layers. One way to do this is to use traits to expose the functionalities of the class. Traits help in hiding the actual implementation.

Consider a service to register a user in the application,

private class UserDaoImpl(implicit ec:ExecutionContext) extends UserDao {
  this: DB =>
def register(user: UserDetails): Future[(Int, Int)] = {
    val loginTableRef = TableQuery[LoginUserTable]
if(validEmailID(user.emailId){
    val action = for {
      ax1 <- (masterTableRef += MasterUser(user.mobNumber,     user.email, user.name, user.age, false))
      ax2 <- (loginTableRef += LoginUser(user.mobNumber, user.uname, user.password))
    } yield {
      (ax1, ax2)
    }
    db.run(action)
  }
else{
Future((0,0))
}

This service adds the details of the registering user in the database, but other components of the application do not know about the actual Database query. A trait can be used to expose this functionality to other components :

trait UserDao {
  def register(user: UserDetails): Future[(Int, Int)]
}

Other components uses the trait to interact with the functionality, but do not see the actual implementation, ensuring loose coupling.

Using Mockito on the layers

In our unit tests, we do not actually want to insert any data in our database. We just want to work with the output received after register() method is called. For example, register() code first checks if the email ID is valid. If valid, the service registers the user in the database and returns Future((1,1)), else Future((0,0)). The component being tested only requires the output from register() and not actual insertion in the database.

To achieve this, we can mock the trait UserDao and specify what we want the method to return if called in the test.

class UserServiceSpec extends FlatSpec with Matchers with MockitoSugar with ScalaFutures {
  //(1)
  val mockDao = mock[UserDao]
  
  //Test user with valid Email ID
  val testUserValidEmailId = UserDetails("7546981235", "testing", "[email protected]", 25, "test123", "test@123")

  //Test user with invalid Email ID
  val testUserInvalidEmailId = UserDetails("7845123695", "dummy", "[email protected]", 25, "dummy123", "dummy@123")
  
  //(2)
 when(mockDao.register(testUserValidEmailId)).thenReturn(     Future.successful((1, 1)))

 when(mockDao.register(testUserInvalidEmailId)).thenReturn(Future.successful((0, 0)))

....}

In the above code, we perform unit test on a component which calls the methods exposed by the UserDao trait.
1) In the first step, we create a mock version of our trait.
val mockDao = mock[UserDao]
2) Next, we specify what the method should return based on the input provided. For example, we specify that the method should return Future((1,1)) if the email ID is valid and Future((0,0)) if the email ID is invalid.

Wherever in our test register() is called with the specified input values, the set output would be returned, without accessing the database. The returned values can then be used to test other features of the component.

For complete implementation of the code, you can follow the given link
https://github.com/swantikag/TestingWithMockito/tree/develop

Configuration for using Mockito

To use Mockito framework, add it as a dependency in build.sbt file as

libraryDependencies += "org.mockito" % "mockito-all" % "version"

To use Mockito for unit tests, the necessary imports are:

import org.scalatestplus.mockito.MockitoSugar
import org.mockito.Mockito._

Finally, you need to mix the MockitoSugar trait with Test classes like

class UserServiceSpec extends FlatSpec with Matchers with MockitoSugar { //
}
case masterNodeRssUpdate: MasterNodeRssUpdate =>
      this.copy(dataRefs = dataRefs.copy(memDataState = Some(dataRefs.memDataState.getOrElse(MemDataState()).copy(masterNodeRssState =
        Some(dataRefs.memDataState.getOrElse(MemDataState()).masterNodeRssState.getOrElse(MasterNodeRssState()).copy(rssStateVector =
          masterNodeRssUpdate.updatedMasterNodeRss, sysmgrPid = masterNodeRssUpdate.sysmgrPid))))))

Once configured, you can now start creating mock objects in unit tests using Mockito framework.

Conclusion

Using Mockito is not just a matter of adding another dependency to your code. It requires changing how we think about unit tests while removing a lot of boilerplate. It cleans our tests and makes it easier to understand. To realise it’s full potential, it’s better to make the underlying layers independent enough. This makes sure that each tier can be tested without worrying about any external dependency.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK