I'm trying to implement unit test using Spock framework this is what my test looks like:
def setup() {
scrollableResultsMock = Mock(ScrollableResults)
paymentRepositoryMock = Mock(PaymentRepository)
paymentRegistryService = new PaymentRegistryService(paymentRepositoryMock)
}
#Unroll
def 'should correctly process'() {
given:
paymentRepositoryMock.findUnconfirmedTransactions(_ as LocalDate, _ as Days) >> scrollableResultsMock
...
}
Here is class in which I'm trying to inject mocked object:
#Service
open class PaymentRegistryService
#Autowired
constructor(
val paymentRepository: PaymentRepository
) {
#Transactional
open fun parseRegistryFileStream(input: InputStream): LinkedList<Pair<Long, String>> {
...
val registry = paymentRepository.findUnconfirmedTransactions(start, PERIOD)
...
}
}
While running test instead of calling my method real paymentRepository method is being called. I don't understand what could be the reason. LocalDate and Days are from Joda time and finally this is paymentRepository class:
#Repository
#Transactional
open class PaymentRepository : AbstractRepository<Payment, Long>(Payment::class.java) {
fun findUnconfirmedTransactions(start: LocalDate, days: Days): ScrollableResults = criteria().add(
and(
eq("isConfirmed", false),
ge("transactionDateTime", start),
lt("transactionDateTime", start.plus(days))
)).setCacheMode(CacheMode.IGNORE).scroll(ScrollMode.FORWARD_ONLY)
}
Please try this:
open fun findUnconfirmedTransactions(start: LocalDate, days: Days): ScrollableResults
Mocking needs to extend function and Spock won't be able to do so unless function is open in Kotlin.
Related
I have a rest controller using spring webflux and reactor, I am writing unit test for the controller. Please find below the code snippets and help me to write the unit test method to test the .doOnError() block.
I have tried to throw an exception by using Mockito
doThrow(CriticalException.class)
.when(myService).myMethod(object);
This is my unit test:
StepVerifier.create(
Mono.just(
webTestClient.post()
.uri("/endpoint")
.accept(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromObject(requestJson)) //Set the body of the request to the given synchronous Object
//Returns:a Mono with the response
//Act
.exchange() //Perform the exchange
//Assert
.expectStatus().isOk()
.expectBody(Agreement.class)
.returnResult()
.getResponseBody()))
.expectNextMatches(agreementResponse -> {
assertNotNull(agreementResponse.getAgreementParticipant());
return true;
})
.expectComplete()
.verify();
This is my controller:
return Mono.fromCallable(() -> {
myService.myMethod(object);
return object;
}).log().subscribeOn(Schedulers.elastic())
.map(p -> ResponseEntity.ok(p))
.defaultIfEmpty(ResponseEntity.notFound().build())
.doOnError(e -> {
LOGGER.error(LOG_FORMAT, e.getMessage(), e.getStackTrace());
});
Mockito is not returning exception while myService.myMethod(object) is been called.
Please suggest proper way to write test for .defaultIfEmpty() and .doOnError() blocks.
Instead of throwing CriticalException.class while mocking your myService.myMethod(object) return an exception wrapped in a Mono
For eg :
Mockito.doReturn(Mono.error(Exception::new)).when(service).callableMethod();
Find the sample code snippet below
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
class Service {
public Mono<String> callableMethod() {
return Mono.just("1");
}
}
class Controller {
private Service service;
public Controller(Service service) {
this.service = service;
}
public Mono<String> endpoint() {
return service.callableMethod().doOnError(throwable -> {
System.out.println("throwable = " + throwable);
});
}
}
public class TestClass {
#Mock
private Service service = Mockito.mock(Service.class);
#Test
public void controllerTest() {
Mockito.doReturn(Mono.error(Exception::new)).when(service).callableMethod();
StepVerifier.create(new Controller(service).endpoint()).verifyError();
}
}
So i have repository, which provides the Observable to the client. Is there a way i can mock this repository, so i don't need to send location from my emulator or using the real device to gain some Location?
Here is how the interface looks like:
interface RxLocationRepository {
#SuppressLint("MissingPermission")
fun onLocationUpdate(): Observable<Location>
fun stopLocationUpdates()
}
In my client side i use this like this:
class LocationManager(
val rxLocationRepository: RxLocationRepository){
private fun appendGeoEvent(location: Location) {
val locationGeoEvent = LocationGeoEvent(
accuracy = location.accuracy.toDouble(),
latitude = location.latitude,
longitude = location.longitude,
timestampGeoEvent = location.time
)
processGeoEvent(locationGeoEvent)
}
compositeDisposable.add(rxLocationRepository.onLocationUpdate()
.subscribe(Consumer { location ->
appendGeoEvent(location)
}))
....
So i sending this obtained location to my appendGeoEvent method.
I can use for example Mockito, but i don't know how to mock this repository so i can use the fake locations.
Also, i want to use Kotlin.
If using Mockito, you could do something like this:
import android.location.Location
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito
import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock
import org.mockito.junit.MockitoJUnitRunner
import java.util.*
#RunWith(MockitoJUnitRunner::class)
class LocationsTest{
#Test
fun should_return_one_location() {
val location = Location("test").apply {
latitude = 1.234
longitude = 5.678
// ...
}
val mockRepository = mock(RxLocationRepository::class.java)
`when`(mockRepository.onLocationUpdate()).thenReturn(Observable.just(location))
// use the mock
}
}
I am using: testCompile "org.mockito:mockito-core:2.11.0"
I needed to setup different Dagger module, which just provide me implementation of this repository - it returns different Observable stream.
I set it up like this.
#Module
abstract class StubLocationRepositoryModule {
#Binds
internal abstract fun bindsRxLocationRepository(stubRxLocationRepository: StubRxLocationRepository) : RxLocationRepository
}
I was just using this in my androidTest package in component.
So my implementation was like this:
just an example:
class StubRxLocationRepository #Inject constructor(val stubLocationParameterName: String) : RxLocationRepository {
val location = Location("test").apply {
latitude = 1.234
longitude = 5.678
accuracy = 20f
time = Date().time
}
override fun onLocationUpdate(): Observable<Location> {
Log.v("StubLocationRepository", "CHOSEN PARAMETER: $stubLocationParameterName")
return when (stubLocationParameterName) {
"highFreq" -> Observable.interval(50, TimeUnit.MILLISECONDS)
.flatMap(
{
Observable.just(location)
}
)
.doOnNext{ t: Location -> Log.v("onLocationUpdate", t.toString()) }
else -> Observable.interval(1, TimeUnit.SECONDS)
.flatMap(
{
Observable.just(location)
}
)
.doOnNext{ t: Location -> Log.v("onLocationUpdate", t.toString()) }
}
}
override fun stopLocationUpdates() {
Log.v("StubLocationRepository", "stopLocationUpdates")
}
}
So in my Dagger Component builder i expose a method which will provide me some parameter in which i will depend in the StubRxLocation implementation - it will return me some stream i need for specific test case.
So the component:
#Singleton
#Component(modules = arrayOf(StubLocationRepositoryModule::class,
LocationTestInstrumentalModule::class, StubRssiRepositoryModule::class))
interface LocationTestInstrumentalComponent{
fun locationClient(): LocationClient
#Component.Builder
interface Builder {
#BindsInstance
fun context(context: Context): Builder
#BindsInstance
fun stubLocationRepositoryParameter(stubLocationRepositoryParameter: String): Builder
fun build(): LocationTestInstrumentalComponent
}
}
So in every test i can bring the mocked repository it like this, which will be ready form me to use for that test case:
#Test
fun someTest(){
val component = DaggerLocationTestInstrumentalComponent.builder().stubLocationRepositoryParameter("highFreq").context(InstrumentationRegistry.getContext()).build()
val client = component.locationClient()
//i can expose some other methods, not only this 'locationClient' in this Component to return me some classes, like this RxLocationRepository(which will behave as i want) and others
}
Given the following, how do I mock processMessage() using Spock, so that I can check that processBulkMessage() calls processMessage() n times, where n is the number of messages within a BulkMessage?
class BulkMessage {
List messages
}
class MyService {
def processBulkMessage(BulkMessage msg) {
msg.messages.each {subMsg->
processMessage(subMsg)
}
}
def processMessage(Message message) {
}
}
You can use spies and partial mocks (requires Spock 0.7 or newer).
After creating a spy, you can listen in on the conversation between the caller and the real object underlying the spy:
def subscriber = Spy(SubscriberImpl, constructorArgs: ["Fred"])
subscriber.receive(_) >> "ok"
Sometimes, it is desirable to both execute some code and delegate to the real method:
subscriber.receive(_) >> { String message -> callRealMethod(); message.size() > 3 ? "ok" : "fail" }
In my opinion this is not a well designed solution. Tests and design walk hand in hand - I recommend this talk to investigate it better. If there's a need to check if other method was invoked on an object being under test it seems it should be moved to other object with different responsibility.
Here's how I would do it. I know how visibility works in groovy so mind the comments.
#Grab('org.spockframework:spock-core:0.7-groovy-2.0')
#Grab('cglib:cglib-nodep:3.1')
import spock.lang.*
class MessageServiceSpec extends Specification {
def 'test'() {
given:
def service = new MessageService()
def sender = GroovyMock(MessageSender)
and:
service.sender = sender
when:
service.sendMessages(['1','2','3'])
then:
3 * sender.sendMessage(_)
}
}
class MessageSender { //package access - low level
def sendMessage(String message) {
//whatever
}
}
class MessageService {
MessageSender sender //package access - low level
def sendMessages(Iterable<String> messages) {
messages.each { m -> sender.sendMessage(m) }
}
}
It does not use Spock built-in Mocking API (not sure how to partially mock an object), but this should do the trick:
class FooSpec extends Specification {
void "Test message processing"() {
given: "A Bulk Message"
BulkMessage bulk = new BulkMessage(messages: ['a', 'b', 'c'])
when: "Service is called"
def processMessageCount = 0
MyService.metaClass.processMessage { message -> processMessageCount++ }
def service = new MyService()
service.processBulkMessage(bulk)
then: "Each message is processed separately"
processMessageCount == bulk.messages.size()
}
}
For Java Spring folks testing in Spock:
constructorArgs is the way to go, but use constructor injection. Spy() will not let you set autowired fields directly.
// **Java Spring**
class A {
private ARepository aRepository;
#Autowire
public A(aRepository aRepository){
this.aRepository = aRepository;
}
public String getOne(String id) {
tryStubMe(id) // STUBBED. WILL RETURN "XXX"
...
}
public String tryStubMe(String id) {
return aRepository.findOne(id)
}
public void tryStubVoid(String id) {
aRepository.findOne(id)
}
}
// **Groovy Spock**
class ATest extends Specification {
def 'lets stub that sucker' {
setup:
ARepository aRepository = Mock()
A a = Spy(A, constructorArgs: [aRepository])
when:
a.getOne()
then:
// Stub tryStubMe() on a spy
// Make it return "XXX"
// Verify it was called once
1 * a.tryStubMe("1") >> "XXX"
}
}
Spock - stubbing void method on Spy object
// **Groovy Spock**
class ATest extends Specification {
def 'lets stub that sucker' {
setup:
ARepository aRepository = Mock()
A a = Spy(A, constructorArgs: [aRepository]) {
1 * tryStubVoid(_) >> {}
}
when:
...
then:
...
}
}
I am trying to invent some kind of mocking SecureSocial action generators, or SecureSocial itself to be able to unit-test controller methods.
I've found some approaches, like Unit-testing methods secured with Securesocial annotation and Testing a Play2 application with SecureSocial using dependency injection but the thing is, that in that questions authors, in fact, don't do unit testing, but integration testing.
My unit tests look like this:
trait MockDaoProvider extends IDaoProvider {
def entityDao = entityDaoMock
}
val controller = new MyController with MockDaoProvider
"MyController.list" should {
"return an OK" in {
entityDaoMock.list().returns(List())
val result = controller.list()(FakeRequest())
status(result) must equalTo(OK)
}
}
As one can see, I mocked dependencies to isolate and test the behavior that controller method actually does.
Everything was OK until I used SecuredAction from securesocial for MyController.list method. Now I get an exception, and the test fails. I have no idea how I could mock, stub or override SecuredAction and UserAwareAction objects from securesocial. Still I don't want to convert my tests into route(...) tests. They are intended to test only the controller's behavior.
Have someone encountered the same problem? May be there are any hints how it could be solved?
PS: Play framework 2.2.1, securesocial - 2.1.2
It seem like the author of the code really hasn't emphasized testability, which has forced users to come up with their own novel solutions. This one by user jeantil could be helpful:
class FakeAuthenticatorStore(app:Application) extends AuthenticatorStore(app) {
var authenticator:Option[Authenticator] = None
def save(authenticator: Authenticator): Either[Error, Unit] = {
this.authenticator=Some(authenticator)
Right()
}
def find(id: String): Either[Error, Option[Authenticator]] = {
Some(authenticator.filter(_.id == id)).toRight(new Error("no such authenticator"))
}
def delete(id: String): Either[Error, Unit] = {
this.authenticator=None
Right()
}
}
abstract class WithLoggedUser(val user:User,override val app: FakeApplication = FakeApplication()) extends WithApplication(app) with Mockito{
lazy val mockUserService=mock[UserService]
val identity=IdentityUser(Defaults.googleId, user)
import helpers._
import TestUsers._
def cookie=Authenticator.create(identity) match {
case Right(authenticator) => authenticator.toCookie
}
override def around[T: AsResult](t: =>T): execute.Result = super.around {
mockUserService.find(Defaults.googleId) returns Some(identity)
UserService.setService(mockUserService)
t
}
}
val excludedPlugins=List(
,"service.login.MongoUserService"
,"securesocial.core.DefaultAuthenticatorStore"
)
val includedPlugins = List(
"helpers.FakeAuthenticatorStore"
)
def minimalApp = FakeApplication(withGlobal =minimalGlobal, withoutPlugins=excludedPlugins,additionalPlugins = includedPlugins)
which then allows testing like this
"create a new user password " in new WithLoggedUser(socialUser,minimalApp) {
val controller = new TestController
val req: Request[AnyContent] = FakeRequest().
withHeaders((HeaderNames.CONTENT_TYPE, "application/x-www-form-urlencoded")).
withCookies(cookie) // Fake cookie from the WithloggedUser trait
val requestBody = Enumerator("password=foobarkix".getBytes) andThen Enumerator.eof
val result = requestBody |>>> controller.create.apply(req)
val actual: Int= status(result)
actual must be equalTo 201
}
After some thinking, probing and experimenting I've ended up with an elegant solution. The solution relies on "cake pattern" of dependency injection. Like this:
Code in controller:
trait AbstractSecurity {
def Secured(action: SecuredRequest[AnyContent] => Result): Action[AnyContent]
}
trait SecureSocialSecurity extends AbstractSecurity with securesocial.core.SecureSocial {
def Secured(action: SecuredRequest[AnyContent] => Result): Action[AnyContent] = SecuredAction { action }
}
abstract class MyController extends Controller with AbstractSecurity {
def entityDao: IEntityDao
def list = Secured { request =>
Ok(
JsArray(entityDao.list())
)
}
}
object MyController extends MyController with PsqlDaoProvider with SecureSocialSecurity
And test code:
trait MockedSecurity extends AbstractSecurity {
val user = Account(NotAssigned, IdentityId("test", "userpass"), "Test", "User",
"Test user", Some("test#user.com"), AuthenticationMethod("userPassword"))
def Secured(action: SecuredRequest[AnyContent] => play.api.mvc.Result): Action[AnyContent] = Action { request =>
action(new SecuredRequest(user, request))
}
}
val controller = new MyController with MockDaoProvider with MockedSecurity
"IssueController.list" should {
"return an OK" in {
entityDaoMock.list().returns(List())
val result = controller.list()(FakeRequest())
status(result) must equalTo(OK)
}
}
Still there is a drawback - the tests depends on securesocial classes as well... but... is it really a drawback?
I don't know how this approach will work in more complex situations, we'll see.
in the controller there is an action:
def delete = {
withDomain {
it.delete()
flash.message = "${message(code: 'default.deleted.message', args: [message(code: 'chocolateBar.label', default: 'ChocolateBar'), it.name])}"
redirect action: 'list'
}
}
which can be tested in development. while in unit test, the message(..) method throws exception ( groovy.lang.MissingMethodException: No signature of method: longtest.ChocolateBarController.message() is applicable for argument types: (java.util.LinkedHashMap) values: [[code:chocolateBar.label, default:ChocolateBar]]):
public void testDelete() {
controller.params.id = '3'
controller.delete()
assert 'list'==controller.redirectArgs.action
}
After study, a mockTagLib method should be called during setup. But found no correct class name for built-in message(..). Please help.
I've solved the problem in unit controller test. like this:
//This is inside Spock test
#Shared
ResourceBundleMessageSource messageSource = null
#Shared
Closure mockMessage = {Map map ->
return messageSource.getMessage((String)map.code, (Object[])map.args, Locale.default)
}
def setupSpec(){
URL url = new File('grails-app/i18n').toURI().toURL()
messageSource = new ResourceBundleMessageSource()
messageSource.bundleClassLoader = new URLClassLoader(url)
messageSource.basename = 'messages'
messageSource.setDefaultEncoding("utf-8")
}
def setup(){
controller.metaClass.message = mockMessage
}
This code is for spock test, but main idea is also available for normal grails test.
In running phase(not test),
calling "message" in controller class results in calling "message" of ValidationTagLib class,
but they are not bind in unit test phase.
So I made almost same logic of "message" of ValidationTagLib,
and bind it(named "mockMessage") to controller.message.
With this code, you can execute "message" correctly in controller class in test.