lundi 1 mai 2023

How I can mock a class marked as final?

I need to add unit tests to the class (SensorClient) that contains a pointer to the object of the class (VelocitySensor) marked as final. And what's important is that I can't modify the VelocitySensor class. Therefore, I can't change VelocitySensor methods to virtual... Also, I would like to avoid any "friend" classes or tricks with #define.

VelocitySensor class:

//sensor_service.hpp
#pragma once
#include <string>

namespace sensor
{
    class VelocitySensor final
    {
    public:
        VelocitySensor() : velocity(20), sensorName("VelocitySensor") {}

        double getVelocity() const
        {
            return velocity;
        }

        std::string getSensorName() const
        {
            return sensorName;
        }

        void calculateSomething()
        {
            const double time = 10;
            auto vel = getVelocity();
            auto distance = vel * time;
            //...
        }
    private:
        std::string sensorName;
        double velocity;
    };
}

SensorClient class:

//sensor_client.hpp
#pragma once
#include "sensor_service.hpp"
#include <memory>
#include <iostream>

namespace sensor
{
    class SensorClient
    {
    public:
        SensorClient() : sensor(std::make_unique<VelocitySensor>()) {}

        double getVelocity() const
        {
            return sensor->getVelocity();
        }

        void calculateNumOfTyres()
        {
            sensor->calculateSomething();
        }

        std::string getVehicleName() const
        {
            return sensor->getSensorName();
        }

    private:
        std::unique_ptr<VelocitySensor> sensor;
    };
};

I found three ways to solve that issue. Maybe there is another better solution (I hope so :))

  1. The first one is to templetize SensorCLient, and as a result, it should be quite easy to mock VelocitySensor during tests.
#pragma once
#include "sensor_service.hpp"
#include <memory>
#include <iostream>


namespace sensor
{
    template <typename T>
    class SensorClientTemplate
    {
    public:
        SensorClientTemplate(std::unique_ptr<T> _sensor = std::make_unique<VelocitySensor>())
            : sensor(std::move(_sensor)) {}
        virtual ~SensorClientTemplate() = default;

        double getVelocity() const
        {
            return sensor->getVelocity();
        }

        void calculateSomething()
        {
            sensor->calculateSomething();
        }

        std::string getSensorName() const
        {
            return sensor->getSensorName();
        }

    private:
        std::unique_ptr<T> sensor;
    };

    using SensorClient = SensorClientTemplate<SensorService>;
};

For me it looks quite good. But maybe there is another solution which doesn't require the use of templates.

  1. The second one is to use the Adapter Pattern.
#pragma once
#include "sensor_service.hpp"
#include <memory>
#include <iostream>

namespace sensor
{
    class ISensor
    {
    public:
        virtual ~ISensor() = default;

        virtual double getVelocity() const = 0;
        virtual std::string getSensorName() const = 0;
        virtual void calculateSomething() = 0;
    };

    class SensorAdapter : public ISensor
    {
    public:
        void calculateSomething() override
        {
            sensor.calculateSomething();
        }

        double getVelocity() const override
        {
            return sensor.getVelocity();
        }

        std::string getSensorName() const override
        {
            return sensor.getSensorName();
        }

    private:
        VelocitySensor sensor;
    };

    class SensorClient
    {
    public:
        SensorClient(std::unique_ptr<ISensor> _sensor = std::make_unique<SensorAdapter>())
            : sensor(std::move(_sensor)) {}
        virtual ~SensorClient() = default;

        double getVelocity() const
        {
            return sensor->getVelocity();
        }

        void calculateSomething()
        {
            sensor->calculateSomething();
        }

        std::string getSensorName() const
        {
            return sensor->getSensorName();
        }

    private:
        std::unique_ptr<ISensor> sensor;
    };
};

Now I have an issue with adding tests to the adapter class... I moved the issue with mocking VelocitySensor to a new class. I would like to take care of code coverage... So this is not a solution for me.

  1. Pimpl

I think I can use the Pimpl idom to hide implementation details (including a pointer to the VelocityObject) in the cpp file, and as a result, I should be able to mock the implementation (Now I don't need to mock VelocitySensor). But in that case, the implementation won't be covered by unit tests. :( I found something about using factories to solve that issue, but I'm not sure how I can use that in my case,

Aucun commentaire:

Enregistrer un commentaire