class Service1 @Inject()(service2: Service2) {
val url = service2.REDIS_URL
}
class TestService @Inject()(service1: Service1) {
def foo() => {}
}
I have the above 2 classes.
I need to test TestService.foo
. Following is the code that I am trying but its not working.
class TestServiceTest extends org.scalatest.AsyncFunSuite with MockFactory {
val service1Mock = mock[Service1]
....
....
}
While initiating the test cases service2.REDIS_URL
fails with null pointer error.
I am unable to find any answers in the scala mock documentation about how to properly mock services/singleton objects.
Update:
class Service2 @Inject()(){
val REDIS_URL = "some constant"
}
class Service1 @Inject()(service2: Service2){
val redisStr = service2.REDIS_URL
def getUrl = redisStr
}
class TestService @Inject()(service1: Service1){
def foo() = service1.getUrl
}
it should "test properly" in {
val mocks1 = mock[Service1]
}
This is not working
but if we change Service1
to
class Service1 @Inject()()(service2: Service2) {
def url = service2.REDIS_URL
}
it works.
But,
class Service1 @Inject()()(service2: Service2) {
def url = service2.REDIS_URL
config.useSingleServer()
.setAddress(REDIS_URL)
}
Again fails
This is due to service2
being null while the Mock is generated. This is very weird that the class is run while creating the Mock in ScalaTest and it finds service2
to be null causing NPE.
CodePudding user response:
No, you cannot mock singleton objects in Scala. But I don't see any in your code. And you mock services just like any other class in Scala.
I am not sure I understand what your actual problem is, but I will try explain the best I can what I understood so far. As someone already said you have to tell your mock what calls to mock, otherwise of course it has no choice but to return null to whatever tries dereferencing it.
By mixing in MockFactory
this means you are using the mock
method of ScalaMock. A known limitation of ScalaMock is that it does not support mocking of val fields. This is because mocks are generated using macros as anonymous subclasses of the class to mock. But the Scala compiler does not allow overriding of val
fields in subclasses, because val
fields are immutable.
So there is no way you can mock service1.url
, as long as url
remains a val
. A quick fix is converting the url
into a def
, so you can then mock the call to the method url
and that should solve your null pointer issue. Here's that idea in action:
class Service1 @Inject() (service2: Service2) {
def url: String = service2.REDIS_URL
}
class TestService @Inject() (service1: Service1) {
def foo(): String = "this is " service1.url
}
// ...
import org.scalamock.scalatest.MockFactory
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
class ProgramTest extends AnyWordSpec with Matchers with MockFactory {
"mock Service1 url " in {
val service1Mock = mock[Service1]
val mytestService = new TestService(service1Mock)
(service1Mock.url _).expects().returns("somethingelse")
val res = mytestService.foo()
res shouldBe "this is somethingelse" // true
}
}
This works. No nulls here. If for some reason, you don't want to change the url
into a def
, a better alternative is to switch to mockito-scala
because that can mock fields as well. You don't need ScalaMock for this.
If I understood correctly and your mock of Service1
is still failing with ScalaMock even after changing url
to def
for some unknown reason, then that's one more reason to switch to mockito-scala
. I could not reproduce your null pointer using it. First import this:
libraryDependencies = "org.mockito" %% "mockito-scala" % "1.17.7" % Test
I tested TestService.foo
as follows:
class Service1 @Inject() (service2: Service2) {
val url: String = service2.REDIS_URL
}
class TestService @Inject() (service1: Service1) {
def foo(): String = "this is " service1.url
}
// ...
import org.mockito.MockitoSugar
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
class ProgramTest extends AnyWordSpec with MockitoSugar with Matchers {
"mock Service1 url " in {
val service1Mock = mock[Service1]
val mytestService = new TestService(service1Mock)
when(service1Mock.url).thenReturn("somethingelse")
val res = mytestService.foo()
res shouldBe "this is somethingelse" // true
}
}
And it worked as expected.