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

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

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Understanding the Two Schools of Unit Testing
  • The Most Popular Technologies for Java Microservices Right Now
  • DevOps Fast Forward with Go
  • Clean Unit Testing

Trending

  • How to Write for DZone Publications: Trend Reports and Refcards
  • How to Format Articles for DZone
  • Beyond Microservices: The Emerging Post-Monolith Architecture for 2025
  • How Clojure Shapes Teams and Products
  1. DZone
  2. Software Design and Architecture
  3. Security
  4. Breaking Static Dependency: How to Make Code Testable

Breaking Static Dependency: How to Make Code Testable

Learn how you can make code with static dependencies testable.

By 
Gunnar Peipman user avatar
Gunnar Peipman
·
Feb. 19, 19 · Tutorial
Likes (3)
Comment
Save
Tweet
Share
22.6K Views

Join the DZone community and get the full member experience.

Join For Free



Static dependency can be a nightmare for developers who write tests for their code. There is not much to do to get rid of static dependencies if they come with third-party libraries or NuGet packages. This blog post introduces two tricks to make code with static dependencies testable.

As a sample, I take one piece of non-commercial code that has a famous static dependency.

public class GetGpsCommand : ICommand<PhotoEditModel>
{
    public bool Execute(PhotoEditModel model)
    {
        var img = ImageFile.FromStream(model.File.OpenReadStream());
        var latObject = (GPSLatitudeLongitude)img.Properties.FirstOrDefault(p => p.Name == "GPSLatitude");
        var lonObject = (GPSLatitudeLongitude)img.Properties.FirstOrDefault(p => p.Name == "GPSLongitude");

        if (latObject != null && lonObject != null)
        {
            model.Latitude = latObject.ToFloat();
            model.Longitude = lonObject.ToFloat();
        }

        return true;
    }
}


As soon as we want to cover the Execute() method with tests, we have to run theFromStream() method of the ImageFile with valid data to avoid exceptions. But then, it is not a unit test anymore butan integration test.

Wrapping a Static Dependency to the Client Class

One option is to use the define interface and two implementations – one for tests and one for the application.

Breaking static dependency using interface and client classes

This way, we can wrap the static dependency to the client class that is used the by application, and for this class, we only use integration tests.

Let’s start with implementation. Client classes mean that we also need some data structure to return coordinates. Let’s define the class GpsCoordiates that is a plain Data Transfer Object (DTO). It doesn’t carry any system logic.

public class GpsCoordinates
{
    public float? Latitude { get; set; }
    public float? Longitude { get; set; }
}


Now, we have DTO to carry coordinates, and it’s possible to define the interface for client classes.

public interface IImageClient
{
    GpsCoordinates GetCoordinates(Stream stream);
}


The first client class that we implement is for the application. The code is similar as before, and we just don’t use the model class anymore to assign coordinates This is because we don’t know, right now, where we later put this code. It’s possible we will move it to some service library that is used by multiple applications in solution,

public class ImageClient : IImageClient
{
    public GpsCoordinates GetCoordinates(Stream stream)
    {
        var img = ImageFile.FromStream(stream);
        var lat = (GPSLatitudeLongitude)img.Properties.FirstOrDefault(p => p.Name == "GPSLatitude");
        var lon = (GPSLatitudeLongitude)img.Properties.FirstOrDefault(p => p.Name == "GPSLongitude");

        var result = new GpsCoordinates();
        result.Latitude = lat?.ToFloat();
        result.Longitude = lon?.ToFloat();

        return result;
    }
}


For tests, we define a simple test client that is easy to control from the unit tests.

public class TestImageClient : IImageClient
{
    public GpsCoordinates CoordinatesToReturn;
    public Exception ExceptionToThrow;

    public GpsCoordinates GetCoordinates(Stream stream)
    {
        if(ExceptionToThrow != null)
        {
            throw ExceptionToThrow;
        }

        return CoordinatesToReturn;
    }
}


If we want it to throw an exception, then we can assign an exception to the ExceptionToThrow field. If we want it to return coordinates, then we can assign a value to the CoordinatesToReturn field.

Before using these classes, we need to modify the original class, so it gets the instance of the image client through the constructor.

public class GetGpsCommand : ICommand<PhotoEditModel>
{
    private readonly IImageClient _imageClient;

    public GetGpsCommand(IImageClient imageClient)
    {
        _imageClient = imageClient;
    }

    public bool Execute(PhotoEditModel model)
    {
        var coordinates = _imageClient.GetCoordinates(model.File.OpenReadStream());

        if (coordinates != null && coordinates.Latitude != null && coordinates.Longitude != null)
        {
            model.Latitude = coordinates.Latitude;
            model.Longitude = coordinates.Longitude;
        }

        return true;
    }
}


Here is one sample test where TestImageClient is used to avoid static dependency.

[Fact]
public void Execute_should_not_assign_if_latitude_is_null()
{
    var coordinates = new GpsCoordinates { Latitude = null, Longitude = 24 };
    var imageClient = new TestImageClient { CoordinatesToReturn = coordinates };
    var model = new PhotoEditModel();

    var command = new GetGpsCommand(imageClient);
    command.Execute(model);

    Assert.Null(model.Latitude);
    Assert.Null(model.Longitude);
}


If latitude is missing but longitude is present, then the coordinate properties of the model are not assigned.

Using a Fake Class

Another approach is to use the fake class and virtual method to return coordinates. It means that, in application and integration tests, we use a method that contains static dependency, and in tests, we use a fake class that overrides the coordinates method, avoiding static dependency to the ImageFile class.

Breaking static dependency using fake class

Let’s start with the command class. Here is the code of the original command again.

public class GetGpsCommand : ICommand<PhotoEditModel>
{
    public bool Execute(PhotoEditModel model)
    {
        var img = ImageFile.FromStream(model.File.OpenReadStream());
        var latObject = (GPSLatitudeLongitude)img.Properties.FirstOrDefault(p => p.Name == "GPSLatitude");
        var lonObject = (GPSLatitudeLongitude)img.Properties.FirstOrDefault(p => p.Name == "GPSLongitude");

        if (latObject != null && lonObject != null)
        {
            model.Latitude = latObject.ToFloat();
            model.Longitude = lonObject.ToFloat();
        }

        return true;
    }
}


As said before, we need to move static dependency to the virtual method, so we can override it later. For this, we move static dependency to the new GetCoordinates() method.

public class GetGpsCommand : ICommand<PhotoEditModel>
{ 
    internal virtual GpsCoordinates GetCoordinates(Stream stream)
    {
        var img = ImageFile.FromStream(stream);
        var lat = (GPSLatitudeLongitude)img.Properties.FirstOrDefault(p => p.Name == "GPSLatitude");
        var lon = (GPSLatitudeLongitude)img.Properties.FirstOrDefault(p => p.Name == "GPSLongitude");

        var result = new GpsCoordinates();
        result.Latitude = lat?.ToFloat();
        result.Longitude = lon?.ToFloat();

        return result;
    }

    public bool Execute(PhotoEditModel model)
    {
        var coordinates = GetCoordinates(model.File.OpenReadStream());
        if (coordinates != null && coordinates.Latitude != null && coordinates.Longitude != null)
        {
            model.Latitude = coordinates.Latitude;
            model.Longitude = coordinates.Longitude;
        }

        return true;
    }
}


Now, we have static dependency under our control and we can write fake command. The GetCoordinates() method of the fake command replaces one in the parent class, and this way, we can avoid static dependency in our unit tests.

public class FakeGetGpsCommand : GetGpsCommand
{
    public GpsCoordinates CoordinatesToReturn;
    public Exception ExceptionToThrow;

    internal override GpsCoordinates GetCoordinates(Stream stream)
    {
        if(ExceptionToThrow != null)
        {
            throw ExceptionToThrow;
        }

        return CoordinatesToReturn;
    }
}


Here is an example test that uses the fake class we created.

[Fact]
public void Execute_should_not_assign_if_latitude_is_null()
{
    var coordinates = new GpsCoordinates { Latitude = null, Longitude = 24 };
    var model = new PhotoEditModel();
    var command = new FakeGetGpsCommand { CoordinatesToReturn = coordinates };

    command.Execute(model);

    Assert.Null(model.Latitude);
    Assert.Null(model.Longitude);
}


It’s not much longer or more complex than the test we wrote for the client classes approach.

Which One Is Better — Fake Class or Interface and Client Classes?

My answer is very common in the world of software development — it depends! Both approaches have their own pros and cons. It depends heavily on how testable the class is built. Based on my own experiences, I can bring out some very general pros and cons.

Approach Pros Cons
Client class
  • More flexible and extensible
  • Leads to a cleaner design
  • Easier to control
  • More new code units
  • The complexity of the system grows
  • Easy to use it too much
Fake class
  • A smaller amount of new code units
  • Most changes are in class scope
  • Hard to use with more complex code
  • Complexity grows faster

Wrapping Up

Static dependency is a horror keyword in unit testing. If it’s our own code, then we can change it. If static calls are forced by the external library or NuGet package, then we actually cannot get away from it. It will always be there, and the best thing we can do is avoid it. This blog post introduced two methods to get rid of static dependency — using an interface with client classes and creating fake class that overrides method with static dependency. Both of these methods have their pros and cons. It’s up to developer to decide which approach is better for static dependency in given class to be broken.

Dependency unit test

Published at DZone with permission of Gunnar Peipman, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Understanding the Two Schools of Unit Testing
  • The Most Popular Technologies for Java Microservices Right Now
  • DevOps Fast Forward with Go
  • Clean Unit 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!