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

  • Techniques You Should Know as a Kafka Streams Developer
  • Microservices Testing: Key Strategies and Tools
  • Top 10 Open Source Projects for SREs and DevOps
  • DevOps Fast Forward with Go

Trending

  • Can You Run a MariaDB Cluster on a $150 Kubernetes Lab? I Gave It a Shot
  • Unlocking Data with Language: Real-World Applications of Text-to-SQL Interfaces
  • Driving DevOps With Smart, Scalable Testing
  • Building an AI/ML Data Lake With Apache Iceberg
  1. DZone
  2. Data Engineering
  3. Data
  4. Improve Microservice Testing With Contract Testing

Improve Microservice Testing With Contract Testing

A tutorial on how to conduct better microservices tests and how the open source PactJS library can help test microservices designed in several languages.

By 
Francisco Moreno user avatar
Francisco Moreno
DZone Core CORE ·
Mar. 05, 19 · Tutorial
Likes (6)
Comment
Save
Tweet
Share
20.2K Views

Join the DZone community and get the full member experience.

Join For Free

Today, it is strange to find a system built in a "monolithic" way. It is becoming more common, because of the advantages that this involves, to divide it into smaller components that communicate with each other to meet the expected needs.

This means that the functionality is not concentrated in a single point, but it is the collaboration of all the parties that gives meaning to the system.

It is common that each of these parts has programmed unit tests that verify that the component behaves correctly in isolation. However, as we all know, that each module works properly individually does not guarantee that the system behaves properly together.

At this point, it could be thought that some integration tests of the complete system would be the solution, and to a certain extent they are, however, in many cases, it is not easy to perform this type of test either because of its complexity, or because "running" the whole system is not simple, because dependencies, unfinished parts, etc. In short, there are many factors that can make a complete set of integration tests in a microservice-oriented environment highly complex.

In this type of situation, it is where approaches such as contract oriented testing provide greater value. In essence, it is about establishing what the consumer expects to receive before certain requests and subsequently verifying that the producer is indeed sending the expected responses, both in form and in data. These contracts are generated by the mock-up of the server, therefore, they are created before starting the development of the server, so that it is guaranteed that it produces at all times the appropriate answers.

This favors the early detection of errors since these tests do not need to have all the system infrastructure raised, only the provider that we are developing and want to validate. Therefore, at the moment that the contract ceases to be fulfilled by said supplier, we will be informed of the error.

There are several libraries that support this type of testing. In this post, I will focus on the use of PactJS.

Contract Testing With PACT

Pact is an open source framework that facilitates the testing of components based on contracts. Its main advantages are:

  • Open source

  • Well documented

  • Active community

  • Support for the main languages (Ruby, .NET, Java, JS, PHP, etc.)

  • "Consumer Driven" Model

  • Consumer-Driven Contract Testing

When making any change in a system where a provider/consumer architecture is followed, the weakest link in the chain will always be the consumer and the one who, mainly, will suffer the effects of any error.

In any service-oriented system, it must be guaranteed at all times that consumers of these services continue to operate normally. That is why you must pay special attention when maintaining compatibility between both systems. This is, mainly, that the format of the requests and answers is the expected one in each part, respectively.

That is why in certain types of solutions it makes more sense for the client to "take the initiative" when defining the rules of communication between the parties. In practice, this approach does not imply that it is the client who "dictates" the rules, in any case, the agreement must arise from a communication between the parties and capture the agreements that have been made.

How Does it Work

The Pact framework establishes a specific workflow and provides a series of utilities that allows it to be carried out.

The main elements that are part of this flow are:

  • Expectations (Interactions)

  • Mock Provider

  • Pact file

  • Mock Consumer

  • Pact Broker (optional)

In summary, as shown in the diagram, the flow could be summarized in two parts: "Play & Record" and "Replay & Verify."

The order of execution would be the following:

  1.  Define the "interactions" between client and provider. That is, how the supplier must respond to specific consumer requests.

  2. Create unit tests that "exercise" the interactions defined above.

  3. Launch such unit tests through Pact.

  4. Pact will automatically start a local service that mocks the behavior of the provider. This makes it so that before the requests are launched by our client, the unit tests will return the previously established answers as the service would do in a real production environment.

  5. This will allow us to verify, before deploying in production, that our client is compatible with the supplier's answers.

  6. If the unit tests are correct, a JSON file containing Pact will be generated.

  7. This file must be shared with the provider.* In each project, the team should look for the most appropriate way to perform this action. Pact's broker can be an interesting option.

  8. We start the provider.

  9. On the consumer side, we launch the Pact verifier indicating the location of the file with Pact.

  10. Pact will raise a mock of the consumer on localhost. This will launch the previously defined calls against our provider and will check the answers with the expected ones.

PactJS

Working with this framework provides the following advantages:

  • Early detection of errors: We verify the compatibility of our systems before deploying the systems.

  • Scalable: We will establish a different pact for each customer-supplier relationship.

  • Parallel work: By mocking both the client and the supplier both parties can work in parallel, thus eliminating dependencies between teams.

  • Multi-technology: Customers and suppliers can be developed in different languages.

Example

The code shown below would correspond to the implementation of Pact in Javascript.

** All the code is available on GitHub.

Installation

To install PACT in a Node environment we must execute the following command:

npm install @ pact-foundation / pact - save-devs

With this, we can already use Pact from our code. However, in order to execute it, we will need a test runner to launch the pertinent verifications. In this example, and for simplicity, we will use the "Mocha + Chai" pair, although it could have been any other option.

npm install mocha chai - save-dev 

Note: This configuration should be done both on the client-side and the server-side. In this case, we assume that both parties are developed on Node.js. Otherwise, you should use the Pact library and corresponding test runners in each case.

In the example on GitHub, can be seen how to perform the verification of a Pact file in the case that the client has developed in Python

Configuration of PACT on the Consumer Side

  • General properties: Specify the name of the consumer and supplier to facilitate debugging, as well as the name and location of the generated agreement

const provider = new Pact({   
    consumer: 'Insert Films Client',
    provider: 'Films Provider',
    port: API_PORT,
    log: path.resolve(process.cwd(), 'logs', 'pact.log'),
    dir: path.resolve(process.cwd(), 'pacts'),
    logLevel: LOG_LEVEL,
    spec: 2
})

Definition of Interactions

We specify the interaction and the unit test that exercises it:

describe("Inserting films", () => {
    before(() => {
        filmService = new FilmsService(endPoint);
        //Start mock service
        return provider.setup();
    })

    after(() => {
        // Generate pact file
        return provider.finalize()
    })
    describe('When insert the meaning of life', () => {
        describe('monty phyton film should be inserted', () => {
            var pact_body = {"id": 42,
                        "Name": "Meaning of life",
                        "Description": "Comedy",
                        "Year": 1986}
            before(() => {
                //Interaction
                //Request and expected answer
                return provider.addInteraction({
                    state: 'Empty repository',
                    uponReceiving: 'Insert meaning of life',
                    withRequest: {
                        method: 'POST',
                        path: '/films/',
                        headers: {
                            'Content-Type': 'application/json'
                        },
                        body: pact_body
                    },
                    willRespondWith: {
                        status: 200,
                        headers: {
                            'Content-Type': 'application/json'
                        },
                        body: pact_body
                    }
                })
            })

            //Unit test that exercises the expectation
            it('film is inserted', () => {
                return filmService.insertFilm(42)
                    .then(response => {
                        expect(response).to.be.not.null;
                        expect(response.id).to.equal(42);
                    });
            });
        })
    });

To generate the corresponding agreement, we would execute the tests:

mocha ./client/test/consumerPact.spec.js - timeout 10000 

*A timeout is added for security since we must leave time for the system to raise the mocked server. *

On the one hand, with this, we would get the result of the tests that would verify that the client is compatible with the definition of the contract and on the other, the JSON file that specifies the contract.

Verify on the Provider-Side

  • The Pact verifier will be responsible for launching the requests against the actual service and checking the answers with the specified ones.

  • Indicate the endpoint of the deployed provider against which the requests will be launched.

  • If necessary, specify the URL of the service that will be used to perform the appropriate set up of the system before the test. 

  • Indicate the location, whether physical or HTTP, of the Pact file (previously generated from the client).

let clienteNormal = {
    provider:"Films Provider",
    providerBaseUrl: 'http://localhost:3000',
    providerStatesSetupUrl: 'http://localhost:3000/init',
    pactUrls: [path.resolve(__dirname, '../../pacts/films_client-films_provider.json')]
};

new Verifier().verifyProvider(clienteNormal).then(() => {
    console.log('success');
    process.exit(0);
}).catch((error) => {
    console.log('failed', error);
    process.exit(1);
});

In the same way, as on the client-side, we would run the tests on the server:

mocha ./api/test/apiPact.spec.js - timeout 10000 

Results

In the execution console, we will see the results of the test: "success." In this case, everything went well. We could also find error messages corresponding to the differences found between what is specified in the agreement and the actual answers.

All the code is available on GitHub.

unit test PACT (interaction design) microservice Open source consumer

Opinions expressed by DZone contributors are their own.

Related

  • Techniques You Should Know as a Kafka Streams Developer
  • Microservices Testing: Key Strategies and Tools
  • Top 10 Open Source Projects for SREs and DevOps
  • DevOps Fast Forward with Go

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!