Python: How you import impacts how you mock

Recently, I had a problem with monkeypatching external service based function. In a nutshell, monkeypatch is a pytest fixture that allows you to replace an object or function with your mock version. Try as I might I could not get it to work. This blog post is something I wish I had read when getting my mocks to work first time.

This is the module structure with get_user function that connects to LDAP. We want to mock get_user in our tests to avoid connecting to LDAP and to not have to use real users for tests.

base.py

def get_user(uun):
    return ca_user

SCENARIO 1: importing get_user function in the module where it’s required

forms.py

from central_auth.base import get_user

def clean_user(uun):
   ca_user = get_user(uun)

Trying to monkeypatch it as below will not have the desired effect. The real get_user still gets called.

tests.py

from central_auth import base

def test_form_POST_OK(monkeypatch):
    monkeypatch(base, 'get_user', get_mock_user_function)

    get_view_with_form() # where get_user is called

# DOES NOT WORK

This is because when we do named importation i.e. importing object or function as opposed to module, the object/function gets a new namespaced name. This way what exists as central_auth.base.get_user is referred to forms.get_user within forms.py.

To make the monkeypatch work, base module should be imported like so:

SCENARIO 2: module import

forms.py

from central_auth import base

def clean_user(uun):
   ca_user = base.get_user(uun)

Then we end up having central_auth.base.get_user and forms.base.get_user, both referring to the same base module.

Alternatively, we can use unittest.mock.patch to the same effect which also allows a greater level of granularity:

SCENARIO 1:

forms.py

from central_auth.base import get_user

def clean_user(uun):
   ca_user = get_user(uun)
tests.py

@mock.patch('forms.get_user', side_effect=get_mock_user_function)
def test_form_OK(form_get_user)

SCENARIO 2:

forms.py

from central_auth import base

def clean_user(uun):
   ca_user = base.get_user(uun)
tests.py

@mock.patch('forms.base.get_user', side_effect=get_mock_user_function)
def test_form_OK(forms_get_user)

The only disadvantage of using mock.patch is that if get_user is called in different modules in your tested function then you need to mock all of them specifically like so:

forms.py

from central_auth import base

def clean_user(uun):
   ca_user = base.get_user(uun)
views.py

from central_auth import base

def view_user(request, uun):
    ca_user = base.get_user(uun)
tests.py

@mock.patch('forms.base.get_user', side_effect=get_mock_user_function)
@mock.patch('views.base.get_user', side_effect=get_mock_user_function)
def test_form_OK(views_get_user, forms_get_user):
    get_view_with_form()

 

Since I am a newbie to python and pytest, give me a shout if I got something terribly wrong. For now, my tests work 🙂

Cheers

Unit testing AngularJS portlet with Maven and Jasmin

Recently, we have converted a few portlets to use AngularJS for the front end.  We have also used the Jasmine framework for unit testing. Since our portlets are already configured with Maven, we have added Jasmine support with jasmine-maven-plugin to the existing set up. This posts will walk you through the configuration required to run Jasmine tests with Maven.

Portlet directory layout

|—— Portlet
| |——– src
| | | ——– main
| | | | ——— webapp
| | | |————- js
                              app.js
| | | ——– test
| | |———— webapp
| | | |————- js
                              controllerSpec.js

Our angularJs files are contained in src/main/webapp/js directory. Jasmine spec files are placed in a test directory that reflects main.

Plugin configuration 

Based on the directory structure, we can configure jasmine-maven-plugin. we are using version 2.0 which requires Maven 3.1.x. This is an example plugin configuration for our portlet:

<build><plugins>
    …
    <plugin>
       <groupId>com.github.searls</groupId>
       <artifactId>jasmine-maven-plugin</artifactId>
       <version>2.0</version>
       <executions>
          <execution>
             <goals>
               <goal>test</goal>
             </goals>
          </execution>
       </executions>
 <!-- keep the configuration out of the execution so that the bdd goal has access to it -->
       <configuration>
           <jsSrcDir>src/main/webapp/js</jsSrcDir>
           <sourceIncludes>
               <include>app.js</include>
           </sourceIncludes>
           <jsTestSrcDir>src/test/webapp/js</jsTestSrcDir>
           <specIncludes>
               <include>*Spec.js</include>
           </specIncludes>
           <preloadSources>
               <source>webjars/angular.js</source>
               <source>webjars/angular-sanitize.js</source>
               <source>webjars/angular-mocks.js</source>
               <source>webjars/jquery.js</source>
           </preloadSources>
       </configuration>
 </plugin></plugins></build>
<dependencies>
 
<!-- jasmin maven plugin dependencies -->
 <dependency>
      <groupId>org.webjars</groupId>
      <artifactId>jquery</artifactId>
      <scope>test</scope>
 </dependency>
 <dependency>
      <groupId>org.webjars</groupId>
      <artifactId>angularjs</artifactId>
      <scope>test</scope>
 </dependency>
<!-- for headful tests
 <dependency>
      <groupId>org.seleniumhq.selenium</groupId>
      <artifactId>selenium-firefox-driver</artifactId>
      <scope>test</scope>
 </dependency>
 -->

 </dependencies>

<jsSrcDir>  points to diectory with your JavaScript, this will be your angular controllers, directives, filters, etc.

<sourceIncludes> specifies which files within jsSrcDir should be included and in which order (added sequentially as defined)

<jsTestSrcDir>  points to directory with your Jasmin specs

<specIncludes> specifies which spec files to include and in which order

<preloadSources> JavaScript files to be loaded before the application files and specs, added sequentially as defined; these should be all libraries that you need for your application e.g. angular files, jQuery etc. We can reference webjars files here as done in our example. Since we require them for the tests only, we reference them in the test scope.

Out of the box, jasmine-maven-plugin comes preconfigured with PhantomJS for headless tests. For headful tests, configure the plugin with a different driver, such as:

<webDriverClassName>org.openqa.selenium.firefox.FirefoxDriver</webDriverClassName>

Usage

Jasmine can be used for TDD (Test Driven Development). Run

mvn jasmine:bdd

This will fire off the browser at http://localhost:8234 and run the specs from the test directory whenever you refresh the page. This will give you an immediate feedback as you develop your angular controllers etc.

Running

mvn jasmine:test

will run the headless tests by default, unless configured otherwise.

Running

mvn clean test

will run all your tests in the portlet, including Jasmine headless tests provided there were no test failures before.

With this configuration we are now ready to write Jasmine specs for our portlets.