I'm trying to have some unit tests using groovy.mock.interceptor. I want to assert that a function was indeed called with some specific values as arguments. I can't find how to do that. Any help?
This is what it looks like:
import groovy.mock.interceptor.MockFor
import org.junit.Test
class MyClassTest extends GroovyTestCase {
#Test
void test_correctness_of_passed_arguments() {
def mock = new MockFor(MyClass)
mock.ignore('main')
mock.demand.myFunction{a, b, c -> '0'} // Is this where I should enforce the input params?
mock.use {
def foo = new MyClass()
foo.main() // <--- this is in there that it gets executed
}
mock.expect.verify()
mock.demand.recorded[0] // <--- can I get what has been passed afterwards?
}
}
You can't achieve expected behavior with MockFor class. Ignoring main method has one significant effect - inner method myFunction gets executed, but it happens without a presence of MockInterceptor. You can put a breakpoint in groovy.mock.MockProxyMetaClass class in the beginning of invokeMethod (line 74) and run a debugger to see what happens.
public Object invokeMethod(final Object object, final String methodName, final Object[] arguments) {
if (null == interceptor && !fallingThrough) {
throw new RuntimeException("cannot invoke method '" + methodName + "' without interceptor");
}
Object result = FALL_THROUGH_MARKER;
if (interceptor != null) {
result = interceptor.beforeInvoke(object, methodName, arguments);
}
if (result == FALL_THROUGH_MARKER) {
Interceptor saved = interceptor;
interceptor = null;
boolean savedFallingThrough = fallingThrough;
fallingThrough = true;
result = adaptee.invokeMethod(object, methodName, arguments);
fallingThrough = savedFallingThrough;
interceptor = saved;
}
return result;
}
Invoking foo.main() method in the mock.use {} block invokes this method for non-null interceptor. The result returned by interceptor.beforeInvoke() is equal to FALL_THROUGH_MARKER because main method is marked as ignored. In this case interceptor is set to null temporarily and the method gets invoked in a regular way - it invokes inner myFunction method, but this fact is not recorded due to null interceptor at this point.
Basically, you treat mock object in your test case not as a mock, but rather as a spy object. Groovy standard mocking library does not support spy objects, but you can use e.g. Spock Framework to write tests using spy objects. The test you have shown in the question could look like this using Spock:
import spock.lang.Specification
class ExampleSpec extends Specification {
static class MyClass {
def main() {
return myFunction(0, 0 ,0)
}
def myFunction(def a, def b, def c) {
return '2'
}
}
def "should call myFunction with specific parameters"() {
given:
def foo = Spy(MyClass)
when:
foo.main()
then:
1 * foo.myFunction(0, 0, 0)
and:
0 * foo.myFunction(1,0,0)
}
}
It executes a real foo.main() method, but it mocks foo.myFunction() method and records invocations and tests if the method got invoked with correct parameters - it records that it got invoked once with parameters (0, 0, 0) and that it was not invoked with parameters (1, 0, 0).
IMPORTANT: If you create mock/spy objects from classes and not interfaces, then you need to add cglib-nodep dependency together with Spock.
Okay, this is doable as mock.demand.myFunction takes a normal Closure.
I ended up with something like this:
import groovy.mock.interceptor.MockFor
import org.junit.Test
class MyClassTest extends GroovyTestCase {
#Test
void test_correctness_of_passed_arguments() {
def mock = new MockFor(MyClass)
mock.ignore('main')
def res = []
// the mocked function stores its values in `res` and returns '0'
mock.demand.myFunction(4) {a, b, c ->
res.add([a, b, c])
'0'
}
mock.use {
def foo = new MyClass()
foo.main() // <--- this is in there that it gets executed
}
mock.expect.verify()
res[0] // <--- I can then access the values there
}
}
In the above example, I request myFunction to be called 4 times.
Related
I have a problem with MockK.
I have a class:
#Service
class ItemServiceImpl(private val varPuObjectMapper: VarPuObjectMapper) : OutboundAdvicesService {
override suspend fun getItemsForWarehouse(warehouseId: String): ItemsDTO {
// do stuff
}
override suspend fun getPickingListsForWarehouse(warehouseId: String): PickingListsDTO {
val groupedOutboundAdvices = getItemsForWarehouse(warehouseId)
// do other stuff
}
}
and a test for this class:
class ItemServiceGroupingTest : FunSpec({
val warehouseId = "1"
val myObjectMapper = MyObjectMapper()
val itemService = mockk<ItemServiceImpl>()
beforeTest {
val items1 = myObjectMapper
.getObjectMapper()
.readValue(Mockups.ITEMS_1, ItemsDTO::class.java)
coEvery {
itemService.getItemsForWarehouse(warehouseId)
} returns items1
}
test("should get items for warehouse with ID 1") {
val itemsDTO = itemService.getItemsForWarehouse(warehouseId)
// assertions here
}
test("should get picking lists for warehouse with ID 1") {
val pickingLists = itemService.getPickingListsForWarehouse(warehouseId)
// assertions here
}
})
Now the first test passes successfully, but the second one fails:
no answer found for: ItemServiceImpl(#1).getPickingListsForWarehouse(1, continuation {})
io.mockk.MockKException: no answer found for: ItemServiceImpl(#1).getPickingListsForWarehouse(1, continuation {})
at app//io.mockk.impl.stub.MockKStub.defaultAnswer(MockKStub.kt:93)
From what I understand, this fails cause the getPickingListsForWarehouse method is not mocked. Is it possible to call a real method using MockK? I tried to use spyk instead of mockk, and I tried mockk with relaxed = true, but it got me nowhere...
The problem with the second test is that you are trying to call a method from a mock without specified behavior. The first test passes because you already set the value which should be returned for the method call itemService.getItemsForWarehouse(warehouseId) in this statement in beforeTest:
coEvery {
itemService.getItemsForWarehouse(warehouseId)
} returns items1
You have to do the same for getPickingListsForWarehouse or call a real method like:
every { itemService.getPickingListsForWarehouse(warehouseId) } answers { callOriginal() }
But then you have to use spyk instead of mock.
However, if you are asserting the object which you provided within the mock, you are not testing the real implementation of the method under test. You are just testing the mock, so if you change the implementation of your method this test still will be passing. beacuse it doesn't call your real object.
I can mock a function of a to be tested class in several ways. But how do I mock an object that is created inside of a to be tested method?
I have this to be tested class
#Grab('org.codehaus.groovy.modules.http-builder:http-builder:0.7')
import groovyx.net.http.HTTPBuilder
class totest {
def get() {
def http = new HTTPBuilder('http://www.google.com')
def html = http.get( path : '/search', query : [q:'Groovy'] )
return html
}
}
How do I mock http.get so I can test the get function:
class TestTest extends Specification {
def "dummy test"() {
given:
// mock httpbuilder.get to return "hello"
def to_test = new totest()
expect:
to_test.get() == "hello"
}
}
A better approach would be to pass the HTTPBuilder into your constructor and then the test code can pass test mocks instead.
But if you want to mock the class construction going on internal to your code, have a look at mocking constructors and classes using GroovySpy and GroovyMock on here: http://spockframework.org/spock/docs/1.0/interaction_based_testing.html
You would need to do something like the below code:
import spock.lang.Specification
import groovyx.net.http.HTTPBuilder
class totest {
def get() {
def http = new HTTPBuilder('http://www.google.com')
def html = http.get( path : '/search', query : [q:'Groovy'] )
return html
}
}
class TestTest extends Specification{
def "dummy test"() {
given:'A mock for HTTP Builder'
def mockHTTBuilder = Mock(HTTPBuilder)
and:'Spy on the constructor and return the mock object every time'
GroovySpy(HTTPBuilder, global: true)
new HTTPBuilder(_) >> mockHTTBuilder
and:'Create object under test'
def to_test = new totest()
when:'The object is used to get the HTTP result'
def result = to_test.get()
then:'The get method is called once on HTTP Builder'
1 * mockHTTBuilder.get(_) >> { "hello"}
then:'The object under test returns the expected value'
result == 'hello'
}
}
What are you testing here? Do you care how the method gets it's result? Surely you care more that it gets the right result? In that case, the method should be changed so the URL is configurable, then you can stand up a server that returns a known string, and check that string is returned
I have a method (method1) that I'd like to test, which based on parameters provided creates an object and calls another method (method2). So I'm mocking method2, which accepts an object (sampleObj).
public void method1(booleanParam) {
if(booleanParam){
List<SampleObj> fooList = new ArrayList<SampleObj>;
fooList.add(new SampleObj("another param"));
anotherService.method2(fooList);
}
//some other smart logic here
}
And here's my test with same obfuscated names (sorry if I missed any typo):
public void testMethod1() {
AnotherService mockedAnotherService = PowerMockito.mock(AnotherService.class);
ServicesFactory.getInstance().setMock(AnotherService.class, mockedAnotherService);
List<SampleObj> fooList = new ArrayList<SampleObj>;
fooList.add(new SampleObj("another param"));
// assert and verify
service.method1(true);
Mockito.verify(mockedAnotherService, times(1)).method2(fooList);
}
The problem is, when I try to mock the anotherService, I need to pass an object to method2, so I have to create a new one. But since it's a new object, it's not the same object, which will be passed from inside the method1, hence the test fails with the exception:
Argument(s) are different! Wanted:
anotherService.method2(
[com.smart.company.SampleObj#19c59e46]
);
-> at <test filename and line # here>
Actual invocation has different arguments:
anotherService.method2(
[com.smart.company.SampleObj#7d1a12e1]
);
-> at <service filename and line # here>
Any ideas how to accomplish that?
You have a few options:
Implement equals and hashCode on SampleObj. Because you didn't wrap fooList in a matcher, Mockito checks with List.equals, which checks equals for corresponding objects in each List. The default behavior of Object.equals is that a.equals(b) iff a == b--that is, objects are equal iff they refer to the same instance--but you're welcome to override that if every SampleObj("foobar") equals every other SampleObj("foobar").
Use a Hamcrest Matcher you write.
private static Matcher<List<SampleObj>> isAListWithObjs(String... strings) {
return new AbstractMatcher<List<SampleObj>>() {
#Override public boolean matches(Object object) {
// return true if object is a list of SampleObj corresponding to strings
}
};
}
// in your test
verify(mockedAnotherService).method2(argThat(isAnObjListWith("another param")));
Note that you could also just make a Matcher of a single SampleObj, and then use a Hamcrest wrapper like hasItem. See more matchers here.
Use a Captor to check equals your own way:
public class YourTest {
// Populated with MockitoAnnotations.initMocks(this).
// You can also use ArgumentCaptor.forClass(...), but with generics trouble.
#Captor ArgumentCaptor<List<SampleObj>> sampleObjListCaptor;
#Test public void testMethod1() {
// ...
verify(mockedAnotherService).method2(sampleObjListCaptor.capture());
List<SampleObj> sampleObjList = sampleObjListCaptor.getValue();
assertEquals(1, sampleObjList.size());
assertEquals("another param", sampleObjList.get(0).getTitle());
}
I do have a simple class like this:
package com.example.howtomocktest
import groovy.util.logging.Slf4j
import java.nio.channels.NotYetBoundException
#Slf4j
class ErrorLogger {
static void handleExceptions(Closure closure) {
try {
closure()
}catch (UnsupportedOperationException|NotYetBoundException ex) {
log.error ex.message
} catch (Exception ex) {
log.error 'Processing exception {}', ex
}
}
}
And I would like to write a test for it, here is a skeleton:
package com.example.howtomocktest
import org.slf4j.Logger
import spock.lang.Specification
import java.nio.channels.NotYetBoundException
import static com.example.howtomocktest.ErrorLogger.handleExceptions
class ErrorLoggerSpec extends Specification {
private static final UNSUPPORTED_EXCEPTION = { throw UnsupportedOperationException }
private static final NOT_YET_BOUND = { throw NotYetBoundException }
private static final STANDARD_EXCEPTION = { throw Exception }
private Logger logger = Mock(Logger.class)
def setup() {
}
def "Message logged when UnsupportedOperationException is thrown"() {
when:
handleExceptions {UNSUPPORTED_EXCEPTION}
then:
notThrown(UnsupportedOperationException)
1 * logger.error(_ as String) // doesn't work
}
def "Message logged when NotYetBoundException is thrown"() {
when:
handleExceptions {NOT_YET_BOUND}
then:
notThrown(NotYetBoundException)
1 * logger.error(_ as String) // doesn't work
}
def "Message about processing exception is logged when standard Exception is thrown"() {
when:
handleExceptions {STANDARD_EXCEPTION}
then:
notThrown(STANDARD_EXCEPTION)
1 * logger.error(_ as String) // doesn't work
}
}
The logger in ErrorLogger class is provided by StaticLoggerBinder, so my question is - how do I make it work so that those checks "1 * logger.error(_ as String)" would work? I can't find a proper way of mocking that logger inside of ErrorLogger class. I have thought about reflection and somehow accessing it, furthermore there was an idea with mockito injection (but how to do that if reference to an object is not even present in that class because of that Slf4j annotation!) Thanks in advance for all your feedback and advices.
EDIT: Here is an output of a test, even 1*logger.error(_) doesn't work.
Too few invocations for:
1*logger.error() (0 invocations)
Unmatched invocations (ordered by similarity):
What you would need to do is to replace the log field generated by the #Slf4j AST transformation with your mock.
However, this is not so easy to achieve, since the generated code is not really test-friendly.
A quick look at the generated code reveals that it corresponds to something like this:
class ErrorLogger {
private final static transient org.slf4j.Logger log =
org.slf4j.LoggerFactory.getLogger(ErrorLogger)
}
Since the log field is declared as private final it is not so easy to replace the value with your mock. It actually boils down to the exact same problem as described here. In addition, usages of this field is wrapped in isEnabled() methods, so for instance every time you invoke log.error(msg) it is replaced with:
if (log.isErrorEnabled()) {
log.error(msg)
}
So, how to solve this? I would suggest that you register an issue at the groovy issue tracker, where you ask for a more test-friendly implementation of the AST transformation. However, this won't help you much right now.
There are a couple of work-around solutions to this that you might consider.
Set the new field value in your test using the "awful hack" described in the stack overflow question mentioned above. I.e. make the field accessible using reflection and set the value. Remember to reset the value to the original during cleanup.
Add a getLog() method to your ErrorLogger class and use that method for access instead of direct field access. Then you may manipulate the metaClass to override the getLog() implementation. The problem with this approach is that you would have to modify the production code and add a getter, which kind of defies the purpose of using #Slf4j in the first place.
I'd also like to point out that there are several problems with your ErrorLoggerSpec class. These are hidden by the problems you've already encountered, so you would probably figure these out by yourself when they manifested themselves.
Even though it is a hack, I'll only provide code example for the first suggestion, since the second suggestion modifies the production code.
To isolate the hack, enable simple reuse and avoid forgetting to reset the value, I wrote it up as a JUnit rule (which can also be used in Spock).
import org.junit.rules.ExternalResource
import org.slf4j.Logger
import java.lang.reflect.Field
import java.lang.reflect.Modifier
public class ReplaceSlf4jLogger extends ExternalResource {
Field logField
Logger logger
Logger originalLogger
ReplaceSlf4jLogger(Class logClass, Logger logger) {
logField = logClass.getDeclaredField("log");
this.logger = logger
}
#Override
protected void before() throws Throwable {
logField.accessible = true
Field modifiersField = Field.getDeclaredField("modifiers")
modifiersField.accessible = true
modifiersField.setInt(logField, logField.getModifiers() & ~Modifier.FINAL)
originalLogger = (Logger) logField.get(null)
logField.set(null, logger)
}
#Override
protected void after() {
logField.set(null, originalLogger)
}
}
And here is the spec, after fixing all the small bugs and adding this rule. Changes are commented in the code:
import org.junit.Rule
import org.slf4j.Logger
import spock.lang.Specification
import java.nio.channels.NotYetBoundException
import static ErrorLogger.handleExceptions
class ErrorLoggerSpec extends Specification {
// NOTE: These three closures are changed to actually throw new instances of the exceptions
private static final UNSUPPORTED_EXCEPTION = { throw new UnsupportedOperationException() }
private static final NOT_YET_BOUND = { throw new NotYetBoundException() }
private static final STANDARD_EXCEPTION = { throw new Exception() }
private Logger logger = Mock(Logger.class)
#Rule ReplaceSlf4jLogger replaceSlf4jLogger = new ReplaceSlf4jLogger(ErrorLogger, logger)
def "Message logged when UnsupportedOperationException is thrown"() {
when:
handleExceptions UNSUPPORTED_EXCEPTION // Changed: used to be a closure within a closure!
then:
notThrown(UnsupportedOperationException)
1 * logger.isErrorEnabled() >> true // this call is added by the AST transformation
1 * logger.error(null) // no message is specified, results in a null message: _ as String does not match null
}
def "Message logged when NotYetBoundException is thrown"() {
when:
handleExceptions NOT_YET_BOUND // Changed: used to be a closure within a closure!
then:
notThrown(NotYetBoundException)
1 * logger.isErrorEnabled() >> true // this call is added by the AST transformation
1 * logger.error(null) // no message is specified, results in a null message: _ as String does not match null
}
def "Message about processing exception is logged when standard Exception is thrown"() {
when:
handleExceptions STANDARD_EXCEPTION // Changed: used to be a closure within a closure!
then:
notThrown(Exception) // Changed: you added the closure field instead of the class here
//1 * logger.isErrorEnabled() >> true // this call is NOT added by the AST transformation -- perhaps a bug?
1 * logger.error(_ as String, _ as Exception) // in this case, both a message and the exception is specified
}
}
If you are using Spring, you have acces to OutputCaptureRule
#Rule
OutputCaptureRule outputCaptureRule = new OutputCaptureRule()
def test(){
outputCaptureRule.getAll().contains("<your test output>")
}
I have a class A which contains an instance of class B, and function foo of A calls function set of B, which updates the state of B. Here is a code example (in Javascript):
A = function () {
this.init = function (b) {
this.b = b
}
this.foo = function (val) {
this.b.set(val)
}
this.bar = function () {
return this.b.get()
}
}
B = function () {
this.set = function (val) {
this.v = val
}
this.get = function () {
return this.v
}
}
How do I unit-test the foo function, while keeping the test for A non-dependent on the implementation of B (using mocks and stubs and what not)?
Using mocks, you can simply hand A a mock of B, which will check that set was called with the appropriate value. If you don't have a mock framework, in JavaScript you can simply create an object:
b = {
setCalled: false,
expectedValue: <expected>
set: function(val) {
<check that val == this.expectedValue>
this.setCalled = true;
}
}
in the test you setup b, create an A with the given b then call A.foo and check that b.setCalled changed to true. You can similarly add a get method to b to check A.bar.
In this case you also should check the smell Feature Envy -- when two classes are this tightly coupled you should check to make certain you are not using something incorrectly. It may be fine in your real example, but it is worth a check.
I figured out the best way to do this, while making sure that A's test doesn't depend on its implementation, would be to create a mock B that has a working get and set, but writes to a temporary variable.
Code example to test A:
// Mock B
b = new function () {
this.set = function (val) {
this.v = val
}
this.get = function () {
return this.v
}
}
// Create an instance of A with Mock B
a = new A().init(b)
// Test A
// ...