DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Integrate Cucumber in Playwright With Java
  • Top 10 Best Practices for Web Application Testing
  • Accessibility Testing vs. Functional Testing
  • Cypress.io — The Rising Future of Web Automation Testing

Trending

  • Memory Leak Due to Time-Taking finalize() Method
  • System Coexistence: Bridging Legacy and Modern Architecture
  • Introduction to Retrieval Augmented Generation (RAG)
  • Software Delivery at Scale: Centralized Jenkins Pipeline for Optimal Efficiency
  1. DZone
  2. Coding
  3. Frameworks
  4. Testing With Jasmine and Maven 3.0

Testing With Jasmine and Maven 3.0

This article will show you how to overcome the testing challenges for a Spring Boot application using Jasmine and Maven 3.0.

By 
Marini Fabio user avatar
Marini Fabio
·
Jan. 30, 18 · Tutorial
Likes (2)
Comment
Save
Tweet
Share
11.6K Views

Join the DZone community and get the full member experience.

Join For Free

There are still a lot of companies who cannot easily migrate or upgrade some technologies, especially ones which are the core of technology stack, and we may need to deal with them for a while. So, I’d like to share my experience in creating a single web application on top of a Spring Boot application (v 1.5) and Maven 3.0.4.

It may be easy, but I faced a few interesting challenges which may be useful in other cases, or the same one, if you have to.

Road to the Code

I really like the approach which starts from the mockup, so first thing first, start from what we really need and how it will be used by the end user. So, first step done, mockup and flow definition:

Mockup

Then it’s time for the architecture and technology stack. We have taken into consideration many aspects, like simplicity, possible evolution, team knowledge, and time.

We ended up with something really basic and easy:

  • Single web page app

  • HTML5

  • JQuery

  • Jasmine

  • And of course, Maven 3.0.5 as the building framework.

Obviously, the option to upgrade Maven version was discarded. The official response was: out of scope and effort above our current “firepower” (time and team resources); but I think it was fear (it is always there in case of technology update/changes), which could be reasonable in a company with a really large business.

Now that we have all the necessary information to start, it’s time for testing!

The Testing Challenges

In order to execute the test, I have added a Maven plugin, which is able to execute Javascript tests written with Jasmine and executed with many drivers (e.g. PhantomJS): Jasmine Maven Plugin.

<build>
...
   <plugins>
       <plugin>
           <groupId>com.github.searls</groupId>
           <artifactId>jasmine-maven-plugin</artifactId>
           <version>2.2</version>
           <executions>
               <execution>
                   <goals>
                       <goal>test</goal>
                   </goals>
               </execution>
           </executions>
           <configuration>
               <jsSrcDir>${project.basedir}/src/main/resources/public/js</jsSrcDir>
               <jsTestSrcDir>${project.basedir}/src/test/javascript</jsTestSrcDir>
           </configuration>
       </plugin>
   </plugins>
...
</build>

;et’s try to execute the Maven test phase, with no tests yet, and there we go: the first problem.

[ERROR] Failed to execute goal com.github.searls:jasmine-maven-plugin:2.2:test (default) on project jasmine-old-mvn: The plugin com.github.searls:jasmine-maven-plugin:2.2 requires Maven version 3.1.0 -> [Help 1]

OK, I cannot use the latest version, so I have to use a previous one, but which one? Not many choices - the most recent version which supports Maven 3.0 is 1.3.1.6.

Unfortunately, this means we are losing a lot of improvements added in version 2.0 (from official website):

  • Upgraded Jasmine version to 2.3.0.

  • Version of the plugin is no longer kept in sync with the Jasmine version.

  • Jasmine is now brought in as a WebJar.

  • Added ability to override the version of Jasmine used. See the documentation for more information.

  • Deprecated configuration parameters are no longer supported.

  • Upgraded Selenium version to 2.45 as well as upgraded many other dependencies.

  • Execution time is written to the test report if available. See #271

  • PhantomJs is now used by default.

  • Uses core of the phantomjs-maven-plugin to automatically download and install PhantomJs.

  • The browserVersion configuration parameter has been deprecated. Use webDriverCapabilitiesinstead.

I want to use JQuery, and there is a fantastic library which helps to write tests in Jasmine with JQuery: Jasmine-Jquery, so our pom.xml will be something like:

<build>
...
   <plugins>
       <plugin>
           <groupId>com.github.searls</groupId>
           <artifactId>jasmine-maven-plugin</artifactId>
           <version>1.3.1.6</version>
           <executions>
               <execution>
                   <goals>
                       <goal>test</goal>
                   </goals>
               </execution>
           </executions>
           <configuration>
               <jsSrcDir>${project.basedir}/src/main/resources/public/js</jsSrcDir>
               <jsTestSrcDir>src/test/javascript</jsTestSrcDir>
               <preloadSources>
            <sorurce>${project.basedir}/src/test/javascript/support/jasmine-jquery.js</sorurce>
               </preloadSources>
           </configuration>
       </plugin>
   </plugins>
...
</build>

And our first test, the login form:

describe('Login form', function () {

   var spyEvent;

   it('should execute the login request', function () {
       spyEvent = spyOnEvent('#login', 'click');

       action.displayLoginForm();
       $('#login').trigger( "click" );

       expect('click').toHaveBeenTriggeredOn('#login');
       expect(spyEvent).toHaveBeenTriggered();
   });
});

I expect a failure because there is nothing yet, and in fact I get:

1 failure:

1.) Login form it should execute the login request <<< FAILURE!

* TypeError: Cannot find function addMatchers in object [object Object]. in http://localhost:45079/spec/support/jasmine-jquery.js (line 376)

* TypeError: $ is not a function, it is undefined. in http://localhost:45079/spec/support/jasmine-jquery.js (line 300)

* TypeError: $ is not a function, it is undefined. in http://localhost:45079/spec/support/jasmine-jquery.js (line 94)

Results: 1 specs, 1 failures

The error about the function $ seems to be right because I haven’t added Jquery yet, however, the first one, about addMatchers, is scaring me. Let’s ignore it for now and add JQuery.

Execute again, and:

[ERROR] Failed to execute goal com.github.searls:jasmine-maven-plugin:1.3.1.6:test (default) on project jasmine-old-mvn: The jasmine-maven-plugin encountered an exception:

[ERROR] java.lang.RuntimeException: org.openqa.selenium.WebDriverException: com.gargoylesoftware.htmlunit.ScriptException: TypeError: Cannot find function addEventListener in object [object HTMLDocument]. (http://localhost:38181/spec/support/jquery-3.2.1.min.js#3)

That’s definitely bad! Something is wrong on the driver which executes the Javascript; indeed, HTMLUnit is the default driver and it seems it does not implement some Javascript features which are used by JQuery.

What can we do? Avoid using JQuery, or use an older version? Not a good idea. Let’s try to use another driver, like PhantomJS:

<dependencies>
...
   <dependency>
       <groupId>com.github.detro.ghostdriver</groupId>
       <artifactId>phantomjsdriver</artifactId>
       <version>1.1.0</version>
       <scope>test</scope>
   </dependency>
...
</dependencies>

<build>
...
   <plugins>
       <plugin>
           <groupId>com.github.searls</groupId>
           <artifactId>jasmine-maven-plugin</artifactId>
           <version>1.3.1.6</version>
           <executions>
               <execution>
                   <goals>
                       <goal>test</goal>
                   </goals>
               </execution>
           </executions>
           <configuration>
               <jsSrcDir>${project.basedir}/src/main/resources/public/js</jsSrcDir>
               <jsTestSrcDir>src/test/javascript</jsTestSrcDir>
               <preloadSources>
                   <sorurce>${project.basedir}/src/test/javascript/support/jquery-3.2.1.min.js</sorurce>
                   <sorurce>${project.basedir}/src/test/javascript/support/jasmine-jquery.js</sorurce>
               </preloadSources>
               <webDriverClassName>org.openqa.selenium.phantomjs.PhantomJSDriver</webDriverClassName>
           </configuration>
       </plugin>
   </plugins>
...
</build>

Try again and we’ll get:

[ERROR] Caused by: java.lang.IllegalStateException: The path to the driver executable must be set by the phantomjs.binary.path capability/system property/PATH variable; for more information, see https://github.com/ariya/phantomjs/wiki. The latest version can be downloaded from http://phantomjs.org/download.html

[ERROR] at com.google.common.base.Preconditions.checkState(Preconditions.java:197)

My fault, PhantomJs needs a specific binary file to execute the code. Also, you can find it in the official jasmine-maven documentation, and thankfully, there is also a way to install it automatically as part of build process, with the PhantomJS Maven Plugin.

We usually test and build our application with CI tools like Jenkins and Bamboo, and from a developer perspective, having automation as much as possible is a must, also for the building machine configuration.

In my case, we were using Bamboo to test and build, so let’s try it:

<build>
...
       <plugin>
           <groupId>com.github.klieber</groupId>
           <artifactId>phantomjs-maven-plugin</artifactId>
           <version>0.8-SNAPSHOT</version>
           <executions>
               <execution>
                   <goals>
                       <goal>install</goal>
                   </goals>
               </execution>
           </executions>
           <configuration>
               <version>1.9.7</version>
           </configuration>
       </plugin>

       <plugin>
           <groupId>com.github.searls</groupId>
           <artifactId>jasmine-maven-plugin</artifactId>
           <version>1.3.1.6</version>
           <executions>
               <execution>
                   <goals>
                       <goal>test</goal>
                   </goals>
               </execution>
           </executions>
           <configuration>
               <jsSrcDir>${project.basedir}/src/main/resources/public/js</jsSrcDir>
               <jsTestSrcDir>src/test/javascript</jsTestSrcDir>
               <preloadSources>
                   <sorurce>${project.basedir}/src/test/javascript/support/jquery-3.2.1.min.js</sorurce>
                   <sorurce>${project.basedir}/src/test/javascript/support/jasmine-jquery.js</sorurce>
               </preloadSources>
               <webDriverClassName>org.openqa.selenium.phantomjs.PhantomJSDriver</webDriverClassName>
               <webDriverCapabilities>
                   <phantomjs.binary.path>${phantomjs.binary}</phantomjs.binary.path>
               </webDriverCapabilities>
           </configuration>
       </plugin>
...
</build>

It’s a fantastic plugin which downloads the PhantomJS binary and sets the variable "${phantomjs.binary}" which can be used to configure Jasmine.

But unfortunately, there is no way to make it work; it requires Maven 3.1 minimum.

No problem, then; let’s install this binary in Bamboo and point to the installation directory.

I have no access to the Bamboo server, so I have to ask the sysadmins to do that.

Response: No way! It is not going to work.

Okay, let’s avoid the details of the discussion I had with them (very boring stuff) and find another solution.

After a lot of research, I came back to the first “solution:” HTMLUnit, which unfortunately does not support JQuery. So, what can I do?

Jquery is an external library, and what do we do with external dependencies on unit tests? We mock them!

Mocking this kind of library could be really challenging, so having a good library which simplifies the work is a must. I have chosen to use Sinon.JS. I like it and it's really easy, but you can choose whatever you want.

So, let’s add it to our test configuration and create some base mocks for JQuery:

<build>
...
   <plugins>
       <plugin>
           <groupId>com.github.searls</groupId>
           <artifactId>jasmine-maven-plugin</artifactId>
           <version>1.3.1.6</version>
           <executions>
               <execution>
                   <goals>
                       <goal>test</goal>
                   </goals>
               </execution>
           </executions>
           <configuration>
               <jsSrcDir>${project.basedir}/src/main/resources/public/js</jsSrcDir>
               <jsTestSrcDir>src/test/javascript</jsTestSrcDir>
               <preloadSources>
                   <sorurce>${project.basedir}/src/test/javascript/support/sinon-2.4.1.js</sorurce>
                   <sorurce>${project.basedir}/src/test/javascript/support/baseMocks.js</sorurce>
               </preloadSources>
           </configuration>
       </plugin>
   </plugins>
...
</build>

The file baseMocks.js is my Javascript file, which contains all base mocks and utilities to build the tests in an isolated environment from JQuery.

This is an excerpt; if you are interested in seeing the full file, just let me know in the comments and I’ll provide it. Take into account that the mocks and the utilities are written to simplify my testing logic, so they could not be appropriate for your cases.

//JQuery mock

// $ [selector]
$ = sinon.stub();

function stubSelector() {
   return sinon.stub({
         val: function(){},
         append: function(){},
         modal: function(){},
         html: function(){},
         submit: function(){},
         unbind: function(){},
         click: function(){},
         getAttribute: function(){},
         prop: function(){},
         parent: function(){},
         clone: function(){},
         appendTo: function(){},
         is: function(){},
         remove: function(){},
         each: function(){},
         find: function(){},
         text: function(){},
         addClass: function(){},
         data: function(){},
         hasClass: function(){},
         get: function(){},
         change: function(){}
  });
}
$.returns(stubSelector());

function mockSelectorListWithEach(data) {
   return {
       each: function(callback) {
           data.forEach(function(elem, index) {
               callback(index, elem);
           });
       },
       fail: function() {}
   };
};

function mockSelectorWithText(textData) {
   return {
       text: function() {
           return textData;
       }
   };
};

function mockInputElement(elementType, elementId, elementValue, cssClass) {
   return {
       type: elementType,
       id: elementId,
       value: elementValue,
       val: function() { return elementValue; },
       getAttribute: function() { return elementId; },
       hasClass: function(requestedClass) { return (cssClass == requestedClass); }
   };
};

//*** $.ajax ***
$.ajax = sinon.stub();

function stubAjaxResponse() {
   return sinon.stub({
       done: function() {},
       fail: function() {}
   });
};
$.ajax.returns(stubAjaxResponse());
...

Now, let’s back to the test and adjust it according to the new mock functions and implement the base login functionality. I want to see a fantastic green line!

describe('Display Login form', function () {
    it('should display the login form and set the submit event', function () {
        var htmlStub = stubSelector();
        $.withArgs('#main').returns(htmlStub);
        var submitStub = stubSelector();
        $.withArgs('#login').returns(submitStub);

        action.displayLoginForm();

        sinon.assert.calledWith($, "#main");
        sinon.assert.calledWith(htmlStub.html, widget.loginForm);

        sinon.assert.calledWith($, "#login");
        sinon.assert.calledOnce(submitStub.submit);
   });
});

It may be a bit difficult to read it at the beginning, but you’ll get used to it really quick. This test verifies that the function “displayLoginForm” sets specific HTML content in the div with id “#main” and set the event “submit” in the input element with id “#login”.

You can also rewrite the test in order to verify which function is given to the submit event, and then test that function in another test.

Finally, we got our environment to make a fantastic single web application in TDD/BDD style! It may take a bit more time to create our specific mock and utility function, but it is definitely worth doing it.

Following are a couple of mock functions that can help you:

The JQuery ajax function:

/*** $.ajax ***/
$.ajax = sinon.stub();

function stubAjaxResponse() {
   return sinon.stub({
       done: function() {},
       fail: function() {}
   });
};
$.ajax.returns(stubAjaxResponse());

function mockAjaxDoneResponse(data, status) {
   return {
       done: function(callback) {
           callback(data, status, null);
       },
       fail: function() {}
   };
};

function mockAjaxFailResponse(status) {
   return {
       done: function() {},
       fail: function(callback) {
           var jqXHR = { responseText: "Error info" };

           callback(jqXHR, status, null);
       }
   };
};

The D3.js functions to create a graph chart:

//d3 mock

function mockD3Force() {
   return {
        nodes   : function(){ return this; },
        links   : function(){ return this; },
        gravity : function(){ return this; },
        charge  : function(){ return this; },
        size    : function(){ return this; },
        on      : sinon.stub(),
        start   : sinon.stub()
   };
}

function mockD3Layout(force) {
   return {
       force: function(){
           return force;
       }
   };
};

function mockD3(layout) {
   return {
        layout    : layout,
        select    : function(){ return this; },
        selectAll : function(){ return this; },
        append    : function(){ return this; },
        data      : function(){ return this; },
        enter     : function(){ return this; },
        attr      : function(){ return this; },
        call      : function(){ return this; },
        text      : function(){ return this; }
   };
};

var forceMock = mockD3Force();
var layoutMock = mockD3Layout(forceMock);

d3 = mockD3(layoutMock);

The Set class! Yes, if you want to use it you have to mock, because it is not implemented in HTMLUnit:

//Set class

function Set() {
   return {
       values: [],
       add: function(element) {
           var alreadyInserted = false;
           this.values.forEach(function(iel) {
               if(iel == element) alreadyInserted = true;
           });

           if(!alreadyInserted) {
               this.values.push(element)
           }
       },
       forEach: function(callback) {
           this.values.forEach(function(element) {
               callback(element);
           });
       }
   };
}

Conclusion

Hopefully, you’ll never face this problem, but if you have to, please don’t give up TDD and automatic tests; you can always find a way to do it.

Apache Maven Jasmine (JavaScript testing framework) Testing JQuery Web application

Opinions expressed by DZone contributors are their own.

Related

  • Integrate Cucumber in Playwright With Java
  • Top 10 Best Practices for Web Application Testing
  • Accessibility Testing vs. Functional Testing
  • Cypress.io — The Rising Future of Web Automation Testing

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!