Skip to content

Commit 1cb37bb

Browse files
authored
Merge pull request #656 from mirkoalicastro/feature/mockstatic
Implement mockStatic
2 parents 99ab7e3 + a1c20e2 commit 1cb37bb

3 files changed

Lines changed: 128 additions & 1 deletion

File tree

common/src/main/scala/org/mockito/MockitoAPI.scala

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,50 @@ private[mockito] trait MockitoEnhancer extends MockCreator {
539539
*/
540540
def spyLambda[T <: AnyRef: ClassTag](realObj: T): T = Mockito.mock(clazz, AdditionalAnswers.delegatesTo(realObj))
541541

542+
/**
543+
* Delegates to <code>Mockito.mockStatic(classToMock: Class[T], mockSettings: MockSettings)</code>. Creates a thread-local mock for all static methods of <code>T</code>. The
544+
* returned controller must be closed after use.
545+
*
546+
* Uses the implicit <code>DefaultAnswer</code> (defaults to <code>ReturnsSmartNulls</code>) just like <code>mock[T]</code>.
547+
*/
548+
def mockStatic[T <: AnyRef: ClassTag](implicit defaultAnswer: DefaultAnswer): MockedStatic[T] =
549+
mockStatic[T](withSettings(defaultAnswer))
550+
551+
/**
552+
* Creates a static mock using a raw Mockito <code>Answer</code> by wrapping it in a scala-mockito <code>DefaultAnswer</code>.
553+
*/
554+
def mockStatic[T <: AnyRef: ClassTag](defaultAnswer: Answer[?]): MockedStatic[T] =
555+
mockStatic[T](DefaultAnswer(defaultAnswer))
556+
557+
/**
558+
* Delegates to <code>Mockito.mockStatic(classToMock: Class[T], mockSettings: MockSettings)</code> with a settings object built from the given <code>DefaultAnswer</code>.
559+
*/
560+
def mockStatic[T <: AnyRef: ClassTag](defaultAnswer: DefaultAnswer): MockedStatic[T] =
561+
mockStatic[T](withSettings(defaultAnswer))
562+
563+
/**
564+
* Delegates to <code>Mockito.mockStatic(classToMock: Class[T], name: String)</code>.
565+
*/
566+
def mockStatic[T <: AnyRef: ClassTag](name: String)(implicit defaultAnswer: DefaultAnswer): MockedStatic[T] =
567+
mockStatic[T](withSettings(defaultAnswer).name(name))
568+
569+
/**
570+
* Delegates to <code>Mockito.mockStatic(classToMock: Class[T], mockSettings: MockSettings)</code>.
571+
*/
572+
def mockStatic[T <: AnyRef: ClassTag](mockSettings: MockSettings): MockedStatic[T] =
573+
Mockito.mockStatic(clazz[T], mockSettings)
574+
575+
/**
576+
* Mocks all static methods of <code>T</code> for the duration of <code>block</code>, and closes the static mock afterwards.
577+
*
578+
* Note: the mock is thread-local &mdash; threads spawned inside the block will see the real static methods, not the mock.
579+
*/
580+
def withStaticMocked[T <: AnyRef: ClassTag](block: MockedStatic[T] => Any)(implicit defaultAnswer: DefaultAnswer): Unit = {
581+
val ms = mockStatic[T]
582+
try { block(ms); () }
583+
finally ms.close()
584+
}
585+
542586
/**
543587
* Mocks the specified object only for the context of the block
544588
*/
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package user.org.mockito.model;
2+
3+
public class JavaStaticFoo {
4+
5+
public static String greet(String name) {
6+
return "hello " + name;
7+
}
8+
9+
public static int sum(int a, int b) {
10+
return a + b;
11+
}
12+
}

scalatest/src/test/scala/user/org/mockito/MockitoSugarTest.scala

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import org.scalactic.Prettifier
1313
import org.scalatest.prop.TableDrivenPropertyChecks
1414
import org.scalatest.{ EitherValues, OptionValues }
1515
import user.org.mockito.matchers.{ ValueCaseClassInt, ValueCaseClassString, ValueClass }
16-
import user.org.mockito.model.JavaFoo
16+
import user.org.mockito.model.{ JavaFoo, JavaStaticFoo }
1717

1818
import org.scalatest.matchers.should.Matchers
1919
import org.scalatest.wordspec.AnyWordSpec
@@ -468,4 +468,75 @@ class MockitoSugarTest extends AnyWordSpec with MockitoSugar with Matchers with
468468
verify(aSpy).apply("hi!")
469469
}
470470
}
471+
472+
"mockStatic[T]" should {
473+
"replace all static methods with defaults and allow stubbing one" in {
474+
val ms = mockStatic[JavaStaticFoo]
475+
try {
476+
JavaStaticFoo.greet("world") shouldBe ""
477+
JavaStaticFoo.sum(2, 3) shouldBe 0
478+
479+
ms.when { () => JavaStaticFoo.greet("world"); () }.thenReturn("mocked!")
480+
481+
JavaStaticFoo.greet("world") shouldBe "mocked!"
482+
JavaStaticFoo.sum(2, 3) shouldBe 0
483+
484+
ms.verify({ () => JavaStaticFoo.greet("world"); () }, times(2))
485+
} finally ms.close()
486+
487+
JavaStaticFoo.greet("world") shouldBe "hello world"
488+
}
489+
490+
"accept a name" in {
491+
val ms = mockStatic[JavaStaticFoo]("namedMock")
492+
try
493+
JavaStaticFoo.sum(1, 2) shouldBe 0
494+
finally ms.close()
495+
}
496+
497+
"accept a raw Answer" in {
498+
val ms = mockStatic[JavaStaticFoo](Answers.CALLS_REAL_METHODS)
499+
try
500+
JavaStaticFoo.sum(4, 5) shouldBe 9
501+
finally ms.close()
502+
}
503+
504+
"accept a DefaultAnswer" in {
505+
val ms = mockStatic[JavaStaticFoo](CallsRealMethods: DefaultAnswer)
506+
try
507+
JavaStaticFoo.greet("x") shouldBe "hello x"
508+
finally ms.close()
509+
}
510+
511+
"accept custom mock settings" in {
512+
val ms = mockStatic[JavaStaticFoo](withSettings.name("customMock"))
513+
try
514+
JavaStaticFoo.sum(4, 5) shouldBe 0
515+
finally ms.close()
516+
}
517+
}
518+
519+
"withStaticMocked[T]" should {
520+
"mock static methods only for the duration of the block and auto-close" in {
521+
JavaStaticFoo.greet("bob") shouldBe "hello bob"
522+
523+
withStaticMocked[JavaStaticFoo] { ms =>
524+
JavaStaticFoo.greet("bob") shouldBe ""
525+
ms.when { () => JavaStaticFoo.greet("bob"); () }.thenReturn("mocked!")
526+
JavaStaticFoo.greet("bob") shouldBe "mocked!"
527+
}
528+
529+
JavaStaticFoo.greet("bob") shouldBe "hello bob"
530+
}
531+
532+
"close the static mock even when the block throws" in {
533+
a[RuntimeException] shouldBe thrownBy {
534+
withStaticMocked[JavaStaticFoo] { _ =>
535+
throw new RuntimeException("boom")
536+
}
537+
}
538+
539+
JavaStaticFoo.greet("alice") shouldBe "hello alice"
540+
}
541+
}
471542
}

0 commit comments

Comments
 (0)