I'm using a Spock test written in Groovy to test some Java code. I'm using JMockit to mock methods in the java code, as Spock only handles mocking Groovy classes. I'm running into a problem where a JMockit MockUp is persisting between tests. Such a mock instance should only exist for the test (per JMockit documentation), but this isn't working, and I imagine it's because it's not using the JMockit test runner, and rather the Spock test runner.
Here is the simplest example of the problem I'm facing. I have a simple method returning a string, I can change the return value of the method with MockUp but it still exists for the third test, which doesn't expect it to be used.
Java Class
public class ClassToTest {
public String method() {
return "original";
}
}
Spock Test
class ClassToTestSpec extends Specification {
void "first test"() {
when:
String result = new ClassToTest().method()
then:
result == "original"
}
void "second test"() {
setup:
new MockUp<ClassToTest>() {
#Mock
public String method() {
return "mocked"
}
}
when:
String result = new ClassToTest().method()
then:
result == "mocked"
}
void "third test"() {
when:
String result = new ClassToTest().method()
then:
result == "original"
}
}
The third test fails, because ClassToTest.method() still returns the String "mocked" rather than "original". Using a debugger I have validated that the Mocked method is called twice.
Question
Is there any way to manually remove a class MockUp in JMockit? Thanks.
You can call the MockUp.tearDown method on the created mockup object, to manually undo its effects.
Not exactly an answer to the question - because I still don't know if JMockit's MockUp can be manually removed. But thanks to #PeterNiederwieser's comments, I found that you can actually create a partial mock for a Java class. Below is the change to the second test from above.
void "second test"() {
setup:
ClassToTest test = Spy(ClassToTest) {
method() >> "mocked"
}
when:
String result = test.method()
then:
result == "mocked"
}
Peter mentioned reconsidering how and what to test if a Spy() is necessary, but for my use case this is preferred.
Related
I have the simple pipeline script:
#!groovy
#org.jenkinsci.plugins.workflow.libs.Library('Some#lib')
import com.cloudbees.groovy.cps.NonCPS
node() {
echo CheekyEnum.getByName('name1').getName()
}
enum CheekyEnum {
ENUM_1('name1', 'f1'),
ENUM_2('name2', 'f2')
String name
String field
CheekyEnum(String name, String field) {
this.name = name
this.field = field
}
static CheekyEnum getByName(String name) {
return values().find { it.name == name }
}
String getName() {
return name
}
}
When I run it everything works OK, but if there is a little change in method getName()
#NonCPS
String getName() {
return name
}
I get a pretty long error stacktrace:
java.lang.StackOverflowError
at java.lang.ClassLoader.loadClass(ClassLoader.java:398)
at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxResolvingClassLoader.lambda$loadClass$0(SandboxResolvingClassLoader.java:51)
at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxResolvingClassLoader.lambda$load$2(SandboxResolvingClassLoader.java:85)
at com.github.benmanes.caffeine.cache.BoundedLocalCache.lambda$doComputeIfAbsent$14(BoundedLocalCache.java:2337)
at java.util.concurrent.ConcurrentHashMap.compute(ConcurrentHashMap.java:1892)
at com.github.benmanes.caffeine.cache.BoundedLocalCache.doComputeIfAbsent(BoundedLocalCache.java:2335)
at com.github.benmanes.caffeine.cache.BoundedLocalCache.computeIfAbsent(BoundedLocalCache.java:2318)
at com.github.benmanes.caffeine.cache.LocalCache.computeIfAbsent(LocalCache.java:111)
at com.github.benmanes.caffeine.cache.LocalManualCache.get(LocalManualCache.java:54)
at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxResolvingClassLoader.load(SandboxResolvingClassLoader.java:79)
...
Why? Doesn't #NonCPS just make method excluded from CPS transformation?
enum is per se a serializable type. So you should better create a wrapper function for it:
import com.cloudbees.groovy.cps.NonCPS
node() {
echo getName(CheekyEnum.getByName('name1'))
}
...
#NonCPS
String getName(CheekyEnum cheeky) {
return cheeky.name
}
The related StackOverflowError could be a bug/smell in the workflow-cps-plugin. Please take a look at its Technical design
Pipeline scripts may mark designated methods with the annotation #NonCPS. These are then compiled normally (except for sandbox security checks).
AFAICS you're running inside a Groovy sandbox. The SandboxInterceptor
is probably generating this stack overflow. Running outside the sandbox should fix your issue as well.
BTW you can also read Pipeline CPS Method Mismatches for a better understanding of what can be called in non-CPS transformed code.
I put this in my Spock test:
GroovyMock( File, global: true)
File.createNewFile() >> null
... which I realise is unorthodox/silly/curious: createNewFile is a non-static method.
The code involved is like this:
if( indexInfoFile.createNewFile() ) {
... it turns out from my experiements that mocking createNewFile like this always returns false, even if you try putting a block in the mock:
GroovyMock( File, global: true)
File.createNewFile() >> {
log.info( 'Hello mum!')
}
... the log message is not printed but createNewFile again returns false.
This is actually what I wanted (i.e. to mock a false return from createNewFile).
Is this intentional, documented behaviour?
PS Caveat: from my experience/experiments today, there is no doubt that this mock method does replace all occurrences of an invocation of this method, on any File instance. However, it appears also to have some alarming side-effects too: for example, a directory I created in my given block before the 2 GroovyMock lines is found NOT to exist afterwards, still in the given block, when I went
myDirPath.toFile().exists()
... I assume this is because toFile involves an invocation of createNewFile...
As documented, Groovy mocks only have additional "magic" when used with Groovy classes, but I assume that you are trying to mock java.io.File, which is a Java JRE class. Thus, the Groovy mock will behave like a normal Spock mock. So I don't know why you want to use the Groovy mock in the first place - maybe because you want to use the global: true feature in order to avoid refactoring for testability in your application class.
As you do not show us an MCVE, I have no way of knowing whether indexInfoFile can be injected into your class/method under test or if it is a dependency created inside the method. In the latter case you need to refactor, it is as simple as that. Dependencies should be injectable, period.
As for your code snippets, there are a few things wrong with them:
Method File.createNewFile() returns boolean, so it does not make any sense to stub it to return null.
When creating a mock, all methods will automatically return false, null or 0, depending on their return type. So there is no need to stub the result for createNewFile() in the first place if you want it to return false because it already does.
You cannot stub an instance method by trying to override it like it was a static method. It makes no sense. Please learn Spock syntax first.
Now, assuming your class under test looks like this (already prepared or refactored for dependency injection via method argument, constructor argument or setter)...
package de.scrum_master.stackoverflow.q59842227;
import java.io.File;
import java.io.IOException;
import java.util.Random;
public class FileCreator {
private static final Random RANDOM = new Random();
public boolean createIndexInfoFile(File indexInfoFile) throws IOException {
if (indexInfoFile.createNewFile()) {
System.out.println("File \"" + indexInfoFile + "\" created");
return true;
}
System.out.println("File \"" + indexInfoFile + "\" NOT created");
return false;
}
public static void main(String[] args) throws IOException {
new FileCreator().createIndexInfoFile(
new File("_abc_" + RANDOM.nextInt(10000) + ".txt")
);
}
}
... then you can test it like this:
package de.scrum_master.stackoverflow.q59842227
import spock.lang.Specification
class FileCreatorTest extends Specification {
def "index info file created"() {
given:
File file = Mock() {
createNewFile() >> true
}
expect:
new FileCreator().createIndexInfoFile(file)
}
def "no index info file created"() {
given:
File file = Mock()
expect:
!new FileCreator().createIndexInfoFile(file)
}
}
See? There is no need for global or Groovy mocks, normal mocks will do just fine. But you need to make your code testable instead of using fancy tricks.
Inside a spock test we want to create a resource and make sure its disposed correctly regardless of the outcome of the test result.
We tried the approach below. But spock is not executing tests when the test code is wrapped inside a closure.
import spock.lang.Specification
class ExampleSpec extends Specification {
def wrapperFunction(Closure cl) {
try {
cl()
} finally {
// do custom stuff
}
}
def "test wrapped in closure"() {
wrapperFunction {
expect:
1 == 1
println "will not execute!"
}
}
}
What is the best approach on creating and disposing a resource inside a spock test.
setup() and cleanup() are not viable solutions since creating and disposing should be possible at arbitrary points inside the feature method.
You can use setup and cleanup block inside of the test case (feature method) like this:
class ReleaseResourcesSpec extends Specification {
void 'Resources are released'() {
setup:
def stream = new FileInputStream('/etc/hosts')
when:
throw new IllegalStateException('test')
then:
true
cleanup:
stream.close()
println 'stream was closed'
}
}
Code from the cleanup block is always executed although the test fails or if there is any exception. See the result of the above example:
So it is similar to setup() and cleanup() methods but in this case you can have different setup and clean up code for each feature method.
I am writing test class for my java class. I am using Junit5 with Mockito.
I am using Junit5 which isnt compatible with Power Mockito so I am using Mockito only.
I have class Emp which have function findSalary like below and EmpProfileClient is initialized at constructor.
Class Emp {
......
public void findSalary(empId) {
...
TaxReturn taxReturn = new TaxReturn(EmpProfileClient);
int value = taxReturn.apply(new TaxReturnRequest.withEmpId(empId))
.returnInRupee();
...
}
}
When I am writing the test case, I mocked EmpProfileClient, but since we are creating TaxReturn in a method, How I can mock TaxReturn.apply so I can write the expectation to get the value as per my choice which I set in the test class?
If you want to mock this, the TaxReturn class should be an injected bean in the Emp class. Add an injection framework (like Spring) and inject the TaxReturn class. In the test you write you can inject a Mock instead of the real class. See #InjectMocks annotation of the mockito framework.
If I understood your question correctly(you are looking for mocking taxReturn.apply) I'd suggest next:
First. Refactor your taxReturn instantiation(as it is would be much easier to mock method behavior in comparison for trying to mock local variable)
public class EmpService {
public int findSalary(Integer empId) {
//...
// It's doesn't matter what the actual empProfileClient type is
// as you mocking creation behavior anyway
Object empProfileClient = null;
TaxReturn taxReturn = createClient(empProfileClient);
int value = taxReturn.apply(new TaxReturnRequest().withEmpId(empId))
.returnInRupee();
//...
return value; // or whatever
}
protected TaxReturn createClient(Object empProfileClient) {
return new TaxReturn(empProfileClient);
}
}
Second. Use Mockito.spy() in your test:
class EmpServiceTest {
#Test
void findSalary() {
TaxReturn taxReturn = Mockito.mock(TaxReturn.class);
// this is the main idea, here you using partial EmpService mock instance
// part is mocked(createClient()) and other part(findSalary()) is tested
EmpService service = Mockito.spy(EmpService.class);
when(service.createClient(any())).thenReturn(taxReturn);
when(taxReturn.apply(any(TaxReturnRequest.class))).thenReturn(taxReturn);
int yourExpectedValue = 5;
when(taxReturn.returnInRupee()).thenReturn(yourExpectedValue);
assertEquals(yourExpectedValue, service.findSalary(0));
}
}
Keep in mind that any(), spy(), when() and mock() methods are part of Mockito API. So there is nothing hidden here
I got error while running JUnit test with Mockito's matcher
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Invalid use of argument matchers!
0 matchers expected, 1 recorded.
This exception may occur if matchers are combined with raw values:
//incorrect:
someMethod(anyObject(), "raw String");
When using matchers, all arguments have to be provided by matchers.
For example:
//correct:
someMethod(anyObject(), eq("String by matcher"));
Here is source code.
class A
{
public String getField()
{
return "hi";
}
}
#Test
public void testA()
{
final A a = mock(A.class);
when(a.getField()).thenReturn(Matchers.any(String.class));
a.getField();
}
What is problem here? Please open my eyes!
You are using an argument matcher: Matchers.any(String.class). An argument matcher is not intended to be used as a return a value of a stubbed method.
An argument matcher should be used when you need to customize the way a method is stubbed :
when(a.sayHello(Matchers.any(String.class))).thenReturn("Hello");
In your example, you must return an instance of String and not a matcher :
when(a.getField()).thenReturn("theFieldValue");
You have made the classic error of mocking the class that you're trying to test. The whole point of mocking is that you remove other classes from consideration by your test. So if you're testing a class A that uses an object of class B in some way, you might make a mock of class B. But you wouldn't mock the class that you're actually testing, because then you are no longer testing the class, but testing the mocking framework.
In order to test the method you've supplied, it doesn't make sense to use Mockito at all; because there's no additional class that you want to remove from consideration by your test. The only class is A - the one you're testing.
Your test should just be the following
public class TestA {
private A toTest = new A();
#Test
public void getFieldReturnsHi() {
assertEquals("Hi", toTest.getField());
}
}