{"action":"created","issue":{"url":"https://api.github.com/repos/twitter/util/issues/124","labels_url":"https://api.github.com/repos/twitter/util/issues/124/labels{/name}","comments_url":"https://api.github.com/repos/twitter/util/issues/124/comments","events_url":"https://api.github.com/repos/twitter/util/issues/124/events","html_url":"https://github.com/twitter/util/pull/124","id":55099241,"number":124,"title":"Easy argument capture for Mockito mocks.","user":{"login":"zacharyvoase","id":55050,"avatar_url":"https://avatars.githubusercontent.com/u/55050?v=3","gravatar_id":"","url":"https://api.github.com/users/zacharyvoase","html_url":"https://github.com/zacharyvoase","followers_url":"https://api.github.com/users/zacharyvoase/followers","following_url":"https://api.github.com/users/zacharyvoase/following{/other_user}","gists_url":"https://api.github.com/users/zacharyvoase/gists{/gist_id}","starred_url":"https://api.github.com/users/zacharyvoase/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zacharyvoase/subscriptions","organizations_url":"https://api.github.com/users/zacharyvoase/orgs","repos_url":"https://api.github.com/users/zacharyvoase/repos","events_url":"https://api.github.com/users/zacharyvoase/events{/privacy}","received_events_url":"https://api.github.com/users/zacharyvoase/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":17,"created_at":"2015-01-22T00:42:01Z","updated_at":"2015-02-02T22:27:09Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/twitter/util/pulls/124","html_url":"https://github.com/twitter/util/pull/124","diff_url":"https://github.com/twitter/util/pull/124.diff","patch_url":"https://github.com/twitter/util/pull/124.patch"},"body":"## Problem\r\nMockito's ArgumentCaptor enables you to capture the arguments that were\r\npassed to stubbed-out methods on mocks, to make assertions on them.\r\nHowever, the API for ArgumentCaptor was built for Java and is kind of\r\nclunky compared to what we expect in Scala.\r\n\r\nAn alternative to using ArgumentCaptor is to use Mockito argument\r\nmatchers in `when(myObj.method(...))`. However, this means every\r\nassertion you want to make has to be encapsulated in a custom matcher\r\nsubclass, and you can't make assertions spanning more than one argument\r\nin each stubbed method invocation. For example, you might want to check\r\nthat a key-value store wrapper object was called with a key of \"foo\" and\r\na timeout of 10 seconds, and Mockito argument matchers would not be able\r\nto provide this.\r\n\r\nAnother alternative is to use matchers in a\r\n`verify(myMock, times(N)).someMethod(...)` call, but this suffers from\r\nthe same problem of having to write a specific matcher subclass for any\r\nnon-standard assertions.\r\n\r\n## Solution\r\nThe ArgumentCapture trait can be mixed into your test class and has two\r\nmethods, `capturingOne` and `capturingAll`, which capture the\r\ninvocations of your stubbed methods as a tuple or Seq of tuples. Here's\r\nan example:\r\n\r\n    when(myObj.lookup(any[String], any[Duration])).thenReturn(someValue)\r\n\r\n    // The call to myObj.lookup will normally live somewhere inside the\r\n    // subject under test.\r\n    assert(myObj.lookup(\"foo\", 10.seconds) === someValue)\r\n\r\n    val (key, timeout) = capturingOne(verify(myObj).lookup _)\r\n    assert(key === \"foo\")\r\n    assert(timeout === 10.seconds)\r\n\r\nAn example which uses `capturingAll` and doesn't make any assertions\r\nabout the order of the stubbed method calls (`forExactly` is defined on\r\nthe `org.scalatest.Inspectors` trait):\r\n\r\n    val requests = capturingAll(verify(myObj, times(2)).lookup _)\r\n\r\n    forExactly(1, requests) { (key, timeout) =>\r\n      assert(key === \"foo\")\r\n      assert(timeout === 10.seconds)\r\n    }\r\n\r\n    forExactly(1, requests) { (key, timeout) =>\r\n      assert(key === \"bar\")\r\n      assert(timeout === 2.seconds)\r\n    }\r\n\r\nThe only issue with this solution is that it currently only has\r\nimplementations up to 5-ary functions; vanilla Scala doesn't have\r\nvariadic type parameters so the captureOne/captureAll functions for each\r\narity have to be copied, pasted and extended by hand currently."},"comment":{"url":"https://api.github.com/repos/twitter/util/issues/comments/72551909","html_url":"https://github.com/twitter/util/pull/124#issuecomment-72551909","issue_url":"https://api.github.com/repos/twitter/util/issues/124","id":72551909,"user":{"login":"vkostyukov","id":1098539,"avatar_url":"https://avatars.githubusercontent.com/u/1098539?v=3","gravatar_id":"","url":"https://api.github.com/users/vkostyukov","html_url":"https://github.com/vkostyukov","followers_url":"https://api.github.com/users/vkostyukov/followers","following_url":"https://api.github.com/users/vkostyukov/following{/other_user}","gists_url":"https://api.github.com/users/vkostyukov/gists{/gist_id}","starred_url":"https://api.github.com/users/vkostyukov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/vkostyukov/subscriptions","organizations_url":"https://api.github.com/users/vkostyukov/orgs","repos_url":"https://api.github.com/users/vkostyukov/repos","events_url":"https://api.github.com/users/vkostyukov/events{/privacy}","received_events_url":"https://api.github.com/users/vkostyukov/received_events","type":"User","site_admin":false},"created_at":"2015-02-02T22:27:09Z","updated_at":"2015-02-02T22:27:09Z","body":"LGTM rather then tiny nitpicking about names."}}