Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mocking java method calls #55

Closed
jevgeni opened this issue Oct 19, 2011 · 16 comments
Closed

Mocking java method calls #55

jevgeni opened this issue Oct 19, 2011 · 16 comments
Labels
Milestone

Comments

@jevgeni
Copy link

jevgeni commented Oct 19, 2011

I am trying to mock java calls using meta constants like this:

(ns midje.test.core
 (:use [midje.sweet]))

(defn write [socket message]
 (.write (.getOutputStream socket) (.getBytes message)))

(fact
 (write ...sock.. "hello") => nil
 (provided
   (.getOutputStream ..sock..) => System/out))

I expect that (.getOutputStream ..sock..) will be replaced with System/
out in the write function call. However, I get "java.lang.Exception:
Unable to resolve var: .getOutputStream in this context (core.clj:
10)"

Considering you know which functions are to be mocked:

(fact ...
 (provided
  (.toUpperCase ..meta..) => "LO"))

it should be possible to perform something like this during macro evaluation:

(gen-interface
 :name midje.Stub
 :methods [[toUpperCase [] String]])

(def stub
 (proxy [midje.Stub] []
  (toUpperCase [] "LO")))

(assuming that interface name is generated and there is some mechanism
to clean compile-path)

Then associate ..meta.. with this stub and use it like this:

user=> (.toUpperCase stub)
"LO"
@AlexBaranosky
Copy link
Collaborator

hi jevgeni, does the gen-interface have to be compiled before the midje code tries to run?

@jevgeni
Copy link
Author

jevgeni commented Oct 21, 2011

Hi Alex, I believe that gen-interface has to be run before the proxy is generated. If metaconstant is using/representing that proxy, then this could be done at the same time, as fact macro evaluated (somewhere at the same time, when "provided" form is evaluated).

@AlexBaranosky
Copy link
Collaborator

In the spirit of brainstorming, I wonder if we could somehow leverage an already existing Java mocking framework, such as Mockito, for the internals?

@jevgeni
Copy link
Author

jevgeni commented Nov 10, 2011

I was thinking about Mockito as well. However, it looks like there is no easy way to generate interface/class, it is expected to be written first. It might have it, but I never encountered that in my daily job. As to the second part of this problem - mocking the existing method - in my opinion proxy is easier solution (just copy-pasting the forms to the method body) rather than using Mockito's mocks.

Another way would be telling which interface/class metaconstant does implements, like this (or something better):

  (provided [^String ..meta.. , ^MyInterface ..anothermeta..]
    (.toUpperCase ..meta..) => "LO"))

In that case Mockito will be quite useful.

@ghost ghost assigned AlexBaranosky Nov 23, 2011
@AlexBaranosky
Copy link
Collaborator

Any more ideas on this jevgeni? I think it is a really useful and sensible feature to work to include in Midje. It sounds like you have some ideas on how to approach it, and I would really appreciate all the ideas you have to offer!

P.S. I wonder about mocking Java static methods? I wonder if Clojure gives us ANY tools to get around the usual impossibility of this...

Test I'd liked to have written tonight:

(fact "access environment vars only when namespace is loaded"
  (do
    (red "a")
    (yellow "b")
    (red "c")
    (yellow "d")) => anything
  (provided
    (System/getenv "MIDJE_COLORIZE") => anything :times 0))

@AlexBaranosky
Copy link
Collaborator

Looks like PowerMock enables mocking Java static method calls:
http://code.google.com/p/powermock/

@AlexBaranosky
Copy link
Collaborator

A couple of proposed syntaxes for creating mocks/stubs (not spies) (via IRC - Alan Malloy)

(provided 
  (mocking bar IBar (.size [this] 10)))
(fact 
  (x) => 1 
  (provided 
    (.foo IFoo f) => 2 
    (.bar IBar b) => 3 
    (.baz IBaz bz ) => 4))
;;the case i'm concerned about is: 
(provided 
  (.foo IBar b) => 10 
  (.bar IBar b) => 9)

@jevgeni
Copy link
Author

jevgeni commented Nov 23, 2011

I haven't used PowerMock, but I guess it should be a good choice as well.

As to syntax - I've just checked my Java code and I see, that I use mocking on the same objects quite intensibly. Won't it be too much duplication if you stick with " (.method SomeLongClassName metaName) => returnValue " form?

I think syntax is the major decision point. Implementation should be quite straighforward as I might imagine (I might be wrong, as I don't know the internals of "fact" macro) - creating an mock instance of the provided class, saving it under ..meta.. symbol, run few powermock invocations such as Mockito.when(mockObj.methodToMock()).thenReturn(123);

@AlexBaranosky
Copy link
Collaborator

After doing some more digging, I'm wondering if the approach to this could be to expand on the idea of metaconstant fakes (...m...)? I think this is what you were getting at jevgeni.

It might look like:

(fact 
  (x ..foo..) => 1 
  (provided 
    (.foo IFoo ..foo..) => 2 ))

@denlab
Copy link

denlab commented Dec 14, 2011

vote +1

@AlexBaranosky
Copy link
Collaborator

I've thought some more about this. In order to get a sytnax like the below we need to do some tricky stuff.

  1. when creating the meta constants, need to create a subclass of Metaconstant that satisfies the given interface.
  2. Let bindings within the body of a fact #1 assumes an interface, what do we do if we want to stub a class? We couldn't piggy back on Metaconstant I don't think...
  3. if we wanted to wrap another mocking library like Mockito, how do we get this to integrate with the Metaconstant?
(fact 
  (x ..foo..) => 1 
  (provided 
    (.foo IFoo ..foo..) => 2 ))

I'm just not seeing a clear way to do this, but I really want to, because Clojure's built on the JVM and imo Midje should make working with the JVM easier for our users...

Any ideas?

@AlexBaranosky
Copy link
Collaborator

Perhaps using the Mockito.spy would work here. It enables you to mock on an object (not class or interface).

So, for a mocked metaconstant the steps would be to make a version of Metaconstant that extends the appropriate interface/protocol, then make a new one of those SubMetaconstants, then Mockito.spy that object.

Meh, we'll see.

@marick
Copy link
Owner

marick commented Jan 16, 2013

I'm thinking I'd like to close this one. I'm averse to tying Midje too closely to Java libraries. I'm also mindful of the slogan "if it's too hard to test, change your design." That is, problems mocking out java methods is a sign that the Java classes should be hidden behind a thin facade. (This is like the old idea that's been around in many guises, such as "the humble dialog box" in GUI development.)

So I'll close it. If anyone wants to reopen, feel free.

@marick marick closed this as completed Jan 16, 2013
@behrica
Copy link

behrica commented Nov 11, 2013

I heard that phrase as well quite often: "if it's too hard to test, change your design." So I adapted my designs accordingly.

Until I came to use jMockit (https://code.google.com/p/jmockit).

And this makes mocking everything (static, final, private) dead easy. It uses a java agent to "re-implement" whatever class on the fly, while the class is loaded. It allows doing amazing stuff.

I have now a Clojure project, in which I want to mock "Thread.sleep". , to make the test run faster.

Using current midje, I need to wrap the Thread.sleep in a public function, for being able to mock it.

Not a big deal, but I need to adapt my design to make it testable....which is not ideal.

@marick
Copy link
Owner

marick commented Dec 9, 2015

I don't see this comment on the github site, but I really doubt it will ever happen. Sorry.

Antonis Kalou wrote:

Is this still on the roadmap? If so, I vote +1


Reply to this email directly or view it on GitHub
#55 (comment).

@kalouantonis
Copy link

Yea, sorry for deleting, I didn't notice the date of the posts until after I commented. Thanks for your help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants