Bndtools: How to properly use a test dependency

95 views
Skip to first unread message

Rubén Pérez Vázquez

unread,
Oct 9, 2020, 4:30:59 PM10/9/20
to bndtools-users
Hi all,

I have *certain* experience with OSGi in the past, but I have never used the Bndtools model, which I believe is very elegant, but I'm struggling to figure out how to do many things. I have googled a lot but I cannot seem to find the answer, so here I am. Apologies if this is question is too basic.

I am trying to write some unit tests for a class and use a mocking library. I find JMockit syntax very elegant but it requires setting the -javaagent parameter when running the tests. How can I add that parameter so that it is applied any time I use Eclipse's "Run as > JUnit test"? Is there any "Bndtools-y" way of doing that?

Because of the previous "problem", I decided to try mockito, because I've seen their .jars in Maven Central are actually OSGi bundles, too. I added mockito-core to the build repository and that sorts out the Java imports, but every time I try to run the tests I get

java.lang.NoClassDefFoundError: net/bytebuddy/dynamic/loading/ClassInjector$UsingReflection

, which means, I assume, that some dependencies are missing. However:

1 - Shouldn't Bndtools be able to figure out the transitive dependencies of mockito-core for me?
2 - If I check the POM of mockit-core, bytebuddy is declared as a compile dependency, so shouldn't it not be required at runtime, i.e. when tests are run?

Thanks in advance for your patience answering my (surely pretty basic) doubts.

Best regards,
Rubén Pérez

Fr Jeremy Krieg (Home)

unread,
Oct 9, 2020, 9:41:30 PM10/9/20
to bndtools-users
Dear Rubén,

I pray that you are well. 

In answer to your questions:

1. There is no "Bndtoolsy" way of setting anything for the plain Run > Junit test. You would configure the javaagent line the same way as you would for non-bndtools. If you're using the "Bnd OSGi Test", you need to use the -runvm parameter. However,  unfortunately ive found JMockit doesn't play nice inside an OSGi framework.
2. Re: transitive dependencies - i assume you're using the MavenBndRepository with a .mvn file? This repo doesn't pull in any transitive dependencies unless they are explicitly listed. This is because Maven POMs often list the wrong dependencies,  or more than you need. However,  if you find the Mockto artifact in the repositories view and right click, you have the option for Bndtools to automatically add compile dependencies and runtime dependencies. 
3. In Maven, compile dependencies are usually required at runtime too. The scope "provided" is normally used if they are not.

We've used Mockito a lot in OSGi testing, and it works well. The resolver should add all the required dependencies to your run file. 

I've assumed in question 2 you're using the bnd workspace build model. The answer is different if you're using Gradle-non workspace or Maven build models. Let me know if that's the case. 

I hope this helps!

Blessings, 
Fr Jeremy Krieg 


--
You received this message because you are subscribed to the Google Groups "bndtools-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to bndtools-user...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/bndtools-users/54024b6e-8260-4ce8-aef9-bb5134521f8fn%40googlegroups.com.

Rubén Pérez Vázquez

unread,
Oct 10, 2020, 7:26:09 AM10/10/20
to bndtool...@googlegroups.com
Dear Fr Jeremy,

Thank you so much for the detailed explanation. It does really help, but I have not still figured out how to make this work.

About the -runvm parameter: if I add this header, will that not apply to any run of the application? That is, there is no way to specify a -runvm that only applies for test runs, does it?

Specifically talking about Mockito, I do use a .mvn repository. Using your tip with the right-click, I have pulled the mockito dependencies into the build.mvn repo:

net.bytebuddy:byte-buddy:1.10.13
net.bytebuddy:byte-buddy-agent:1.10.13
org.objenesis:objenesis:3.1
org.mockito:mockito-core:3.5.13

Then I added (at least, I tried to add) these dependencies to the testpath. I used the following on cnf/build.bnd, since I want for mockito to be available to any tests in the application:

-testpath.mockito: ../cnf/libs/mockito-junit-jupiter-3.5.11.jar;version=file,\
org.mockito.mockito-core;version=3.5.11,\
net.bytebuddy:byte-buddy:1.10.13,\
net.bytebuddy:byte-buddy-agent:1.10.13,\
org.objenesis:objenesis:3.1

However, I get exactly the same errors as before. I inspected the dependency net.bytebuddy:byte-buddy:1.10.13 with the super-useful JAR File Viewer and it does contain the net/bytebuddy/dynamic/loading/ClassInjector$UsingReflection which causes the NoClassDefFounError, so I don't understand what's missing or what I am doing wrong.

Finally, I am curious about the build models you mentioned at the end of your email. I've seen in the Bndtools tutorials that Gradle is merely used for being able to repeat the build in a CI environment, but because I am trying to go step by step, I haven't added any Gradle files yet. However, when (or if!) I find myself comfortable enough to go on making more complex projects, I want to add it. Maybe having some gradle configuration will help Eclipse sort out the dependencies? Or will it just be more confusing?

Thanks in advance for you patience and your time.

Best,
Rubén Pérez

You received this message because you are subscribed to a topic in the Google Groups "bndtools-users" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/bndtools-users/F9LyRkv9aqY/unsubscribe.
To unsubscribe from this group and all its topics, send an email to bndtools-user...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/bndtools-users/CAO6F8YzcXZNCjJhsVxPR3cAwR8YxF%3D_Z87S_dVeU3UZMLuD07A%40mail.gmail.com.

Fr Jeremy Krieg (Home)

unread,
Oct 10, 2020, 9:36:03 AM10/10/20
to bndtools-users
Dear Rubén,  

Please see my answers inline below.

Hope this helps!

Blessings,
Fr Jeremy

On Sat, Oct 10, 2020 at 9:56 PM Rubén Pérez Vázquez <rube...@gmail.com> wrote:
Dear Fr Jeremy,

Thank you so much for the detailed explanation. It does really help, but I have not still figured out how to make this work.


Hang in there, we'll get it... :)
 
About the -runvm parameter: if I add this header, will that not apply to any run of the application? That is, there is no way to specify a -runvm that only applies for test runs, does it?


Well, it is true that if you add -runvm to your launch file, it will apply to all launches of that file - whether launch using "Bnd OSGi Run Launcher" or "Bnd OSGi Test Launcher (JUnit)", it will still apply that -runvm. However, you can have more than one launch file by adding additional ".bndrun" files to your project, and each .bndrun file can have its own -runvm settings. If you need special -runvm settings for your tests, simply have a separate test.bndrun file and add the -runvm settings to it.
 
Specifically talking about Mockito, I do use a .mvn repository. Using your tip with the right-click, I have pulled the mockito dependencies into the build.mvn repo:

net.bytebuddy:byte-buddy:1.10.13
net.bytebuddy:byte-buddy-agent:1.10.13
org.objenesis:objenesis:3.1
org.mockito:mockito-core:3.5.13


Well done - this should be enough to get started.
 
Then I added (at least, I tried to add) these dependencies to the testpath. I used the following on cnf/build.bnd, since I want for mockito to be available to any tests in the application:

-testpath.mockito: ../cnf/libs/mockito-junit-jupiter-3.5.11.jar;version=file,\
org.mockito.mockito-core;version=3.5.11,\
net.bytebuddy:byte-buddy:1.10.13,\
net.bytebuddy:byte-buddy-agent:1.10.13,\
org.objenesis:objenesis:3.1


Ok, you're using the wrong syntax for -testpath. There are two syntaxes:

1. For jars that are bundles, it is: my.bundle.bsn[;version=<version>] 
2. For jars that are not bundles, it is: my.group:my-artifact[;version=<version>]

You've used the syntax my.group:my-artifact:<version>, which is what is used in the .mvn file.

Also, you don't have to manually download mockito-junit-jupiter. You can add this to your .mvn file:

org.mockito:mockito-junit-jupiter:3.5.11

And then reference it in your -testpath using the second syntax (as it is not an OSGi bundle). So your full -testpath.mockito will look like this:
 
-testpath.mockito: org.mockito:mockito-junit-jupiter,\
org.mockito.mockito-core,\
net.bytebuddy.byte-buddy,\
net.bytebuddy.byte-buddy-agent,\
org.objenesis

It's a bit neater without referencing the version numbers, and if you ever change your version numbers in your .mvn file you won't have to repeat yourself here.

However, I get exactly the same errors as before. I inspected the dependency net.bytebuddy:byte-buddy:1.10.13 with the super-useful JAR File Viewer and it does contain the net/bytebuddy/dynamic/loading/ClassInjector$UsingReflection which causes the NoClassDefFounError, so I don't understand what's missing or what I am doing wrong.


Hopefully the above will help.

Note that this is all for testing *without* OSGi running. Testing with OSGI running is something else, and requires you to resolve a bnd or bndrun file and then launch using the "Bnd OSGi Test Launcher (JUnit)"
 
Finally, I am curious about the build models you mentioned at the end of your email. I've seen in the Bndtools tutorials that Gradle is merely used for being able to repeat the build in a CI environment, but because I am trying to go step by step, I haven't added any Gradle files yet. However, when (or if!) I find myself comfortable enough to go on making more complex projects, I want to add it. Maybe having some gradle configuration will help Eclipse sort out the dependencies? Or will it just be more confusing?


I don't want to confuse you further - just know that you are using the Bndtools Workspace model. This is usually the easiest model to work with. You don't need to worry about Gradle just yet - it won't help with your issue.
 
Thanks in advance for you patience and your time.


You're welcome. I'm just trying to repay the favours that others have shown to me. :)
 

Rubén Pérez Vázquez

unread,
Oct 10, 2020, 6:36:57 PM10/10/20
to bndtool...@googlegroups.com
Dear Fr Jeremy,

Thanks so much for your valuable help! It's working now without issues!

Just a last question: so far, I have been running my JUnit tests by placing the cursor on the test name -> right click -> "Run as..." -> JUnit Test. This is fine. However, I have been using JUnit 5 and, in order for the "Run as" menu to show up, I had to previously add the JUnit5 library manually in Eclipse (by doing right click on the project's name -> Properties -> Build Path -> Add library -> JUnit). However, this seemed like a hack, because I believe this dependency should also be managed by bnd.

After some trial and error, I opened the manually imported JUnit library and listed all the JAR files included there. Then, I added them manually to the build.mvn repository, such as:

org.junit.jupiter:junit-jupiter-api:5.7.0
org.junit.jupiter:junit-jupiter-engine:5.7.0
org.junit.jupiter:junit-jupiter-migrationsupport:5.7.0
org.junit.jupiter:junit-jupiter-params:5.7.0
org.junit.platform:junit-platform-commons:1.7.0
org.junit.platform:junit-platform-engine:1.7.0
org.junit.platform:junit-platform-launcher:1.7.0
org.junit.platform:junit-platform-runner:1.7.0
org.junit.platform:junit-platform-suite-api:1.7.0
org.apiguardian:apiguardian-api:1.1.0
org.opentest4j:opentest4j:1.2.0

 , and, finally, to the -testpath as:

-testpath.jupiter: junit-jupiter-api,\
junit-platform-commons,\
junit-jupiter-api,\
junit-jupiter-engine,\
junit-jupiter-migrationsupport,\
junit-jupiter-params,\
junit-platform-commons,\
junit-platform-engine,\
junit-platform-launcher,\
junit-platform-runner,\
junit-platform-suite-api,\
org.apiguardian:apiguardian-api,\
org.opentest4j

 (I did not include the dependency versions as per your advice).

This does bring back the "Run as" Eclipse menu and does not cause any problem. However, it feels clumsier than adding the library in Eclipse (you just choose JUnit 5 and you get all the dependencies without extra effort; also, I would not know the complete dependency list if I did not have one provided by the JUnit library). I have read the "Testing" chapter of the BND documentation, but it looks like this applies for testing bundles only (i.e. integration tests rather than unit tests). Is there a simpler way with Bndtools to add those dependencies?

Best,
Rubén

Fr Jeremy Krieg (Home)

unread,
Oct 11, 2020, 7:58:19 AM10/11/20
to bndtools-users
Dear Rubén,

I'm glad you managed to get it working!

You're absolutely on the right track with supplying the JUnit 5 dependencies via Bnd. It will work by adding JUnit 5 through Eclipse, but only in Eclipse. When the time comes for you to add your Gradle wrapper to make it build from CLI/CI server, it won't work if you do it that way. Adding all the dependencies through Bnd (as you've done) will work both in Eclipse and from Gradle.

You're right that doing it in Bnd on -testpath is slightly more clunky. You could make it slightly less clunky because you've got a couple of duplicates (junit-jupiter-api and junit-platform-commons are listed twice), and there are a couple that most test setups don't need - normally you won't use junit-jupiter-migrationsupport, junit-platform-suite-api and junit-platform-runner. In the long term there might be something that we can do to make the Bndtools experience slightly smoother. But the good news is once you've got it set in your central build.bnd file, you don't have to keep copying it to each new project.

It's true that the Bnd docs are more focused on integration than unit testing. I think this is because unit testing is more widely understood and the JUnit 5 documentation itself should be sufficient. However, on a related note, I am increasingly of the opinion not to bother with unit tests and run only integration tests. In the osgi-test project (https://github.com/osgi/osgi-test), we do exactly that, and more-and-more I'm doing it in my own projects. The reasoning behind this derives from the testing principle that you should only test your code through its public API, and if you're doing an integration OSGi test this means you will only have access to the public API. I came to this realisation when I wrote lots of unit tests and had good coverage, only to find I needed to refactor it when I got to the integration stage. The result was that much of the code that I wrote ended up needing to be rewritten or abandoned entirely, and I had wasted my time not only developing that code, but also the tests for it. I also find that the integration-level tests end up duplicating a lot of what the unit tests were doing, and also that in the unit tests I often had mocks that started off simple but ended up being as complicated as the code that it was supposed to be mocking (in which case, why not simply use that code in the first place - ie, integration test). If you always test at the integration level, you are less likely to fall into the trap of writing code that ends up not being needed because you're focusing on testing the functionality more so than a specific implementation. You can also be confident that your code won't suddenly stop working when deployed in an OSGi environment (this was a lesson we learned in osgi-test with our assertj-implementation, which unit-tested fine but then some things failed in an OSGi environment).

Anyway, I hope that helps and I haven't rambled too much! You're obviously getting your head around it to get as far as you did without additional help so kudos!

Blessings,
Fr Jeremy

Reply all
Reply to author
Forward
0 new messages