Git Product home page Git Product logo

swift-mock's Introduction

SwiftMock

A package to simplify writing tests by automating the process of creating mock objects.

Overview

This package provides a Mock macro that can be applied to a protocol to generate a new type. This type will have a name similar to the protocol name with the Mock suffix.

To work with an object of Mock type, the global when(_:) method is used. For each protocol method, 2 methods will be generated. One method will be similar to the protocol method, the second method will have a prefix in the form of the $ symbol. When you call the when(_:) method you should use the method with a prefix.

The when(_:) method returns a MethodInvocationBuilder that allows you add the stub to Mock object for its methods. In most cases, you should call the builder's MethodInvocationBuilder.thenReturn(_:) method and determine what value stub should return. If the method can throw errors, then you can use the ThrowsMethodInvocationBuilder.thenThrow(_:) method to test error handling.

Mock Creation

First of all, I would like to clarify that at the moment the package allows you to create Mock objects only for public protocols that contain only methods and do not contain any properties.

In order to generate a mock type for your protocol, you need to add the @Mock macro before the protocol keyword.

@Mock
public protocol SomeProtocol {
}

This macro will generate a new public type with the protocol name and the suffix Mock. In our example, the type name will be SomeProtocolMock.

Stubbing basics

Let's add the getAlbumName() method to our protocol, which returns the name of the album with the String type. And let's move on to writing the test.

@Mock
public protocol SomeProtocol {
	func getAlbumName() async throws -> String
}

From this moment on, the generated mock object contains the basic functionality for stubbing protocol methods. To stub methods we must use the when(_:) function. As a function argument, we must indicate which method of which mock object we want to stub. This is done by calling a generated method with an identical name and the $ prefix on the mock object.

func testSomeProtocol() async throws {
	let mock = SomeProtocolMock()
	
	when(mock.$getAlbumName())
}

The when(_:) function returns us a builder, which allows us to determine what our method should return if called. The AsyncThrowsMethodInvocationBuilder.thenReturn(_:) method is used for this.

func testSomeProtocol async throws {
	let mock = SomeProtocolMock()
	
	when(mock.$getAlbumName())
		.thenReturn("I'mperfect")
}

After this, whenever we call the getAlbumName() method, we will receive the value that we specified in the thenReturn(_:) method.

func testSomeProtocol() async throws {
	let mock = SomeProtocolMock()
	
	when(mock.$getAlbumName())
		.thenReturn("I'mperfect")
	
	let albumName = try await mock.getAlbumName()
	
	XCTAssertEqual("I'mperfect", albumName)
}

For more details see: Stubbing

Verification basics

When we stab our mock objects, most often we want to check that the necessary methods on the mock object have been called. For this purpose, there is the verify(_:times:) method, which allows you to check the number of calls of a particular method and with what arguments it was called.

Let's look at the following example. We want to write a test that checks that we requested a name for each album id passed in and check that we get the same album names that we passed into the mock in the correct order.

Let's imagine the following facade above our service

final class SomeFacade {
	var service: SomeProtocol

	init(service: SomeProtocol) {
		self.service = service
	}

	func fetchAlbumNames(_ ids: [String]) async throws -> [String] {
		var albums = [String]
		albums.reserveCapacity(ids.count)
		for id in ids {
			try await albums.append(service.getAlbumName(id: id))
		}
		return albums
	}
}

For this test, first of all, we must create a mock and stab the method with all the necessary data. After that, we create our facade object and inject the mock there. As a final step, we call the facade method we want to test, get the data, check it and check the mock calls.

func testFetchAlbum() async throws {
	let mock = SomeProtocolMock()

	let passedIds = [
		"id1",
		"id2",
		"id3",
		"id4",
	]

	let expected = [
		"#4",
		"Inspiration Is Dead",
		"Just a Moment",
		"Still a Sigure Virgin?",
	]

	for (id, name) in zip(passedIds, expected) {
		when(mock.$getAlbumName(id: eq(id)))
			.thenReturn(name)
	}

	let facade = SomeFacade(service: mock)

	let actual = try await facade.fetchAlbumNames(passedIds)

	XCTAssertEqual(expected, actual)

	for id in passedIds {
		verify(mock).getAlbumName(id: eq(id))
	}
}

For more details see Verifying

swift-mock's People

Contributors

metalheadsanya avatar dependabot[bot] avatar

Stargazers

David Spaeth avatar Martin Gratzer avatar Siddharth avatar Fabian Mücke avatar Angel Peralta avatar jinhu avatar Piero Mattos avatar KIDI'S-TECH avatar Kanagasabapathy Rajkumar avatar houseme avatar SongSeoYoung avatar stefanfessler avatar Sofiane Beors avatar 佐毅 avatar Dídac Coll Pujals avatar Pedro Piñera Buendía avatar Matty Cross avatar buhe avatar  avatar Sergey Akentev avatar Jaemyeong Jin avatar KANG DONGYEONG avatar Lukas Kuhl avatar Tim Kersey avatar Blazej SLEBODA avatar Alexander avatar

Watchers

 avatar  avatar

swift-mock's Issues

Append verifyNoInteractions() function

This function should get mock/mocks and check, that there is no interaction with this mock/mocks at this moment.

For InOrder this should be method without arguments and check that there is no interaction with stored mocks.

Append verifyNoMoreInteractions() function

This function should check that all interactions with mock was checked.

For InOrder this should be method and check that last there is no more interactions with passed mocks after last checked.

Point verify failure to `verify` call

If one sets testFailureReport to { XCTFail($0) } and verify fails the test, there's no clear indication of why the test failed. One needs to go to the Issue navigator in Xcode to see the exact failure. It would be nice if the test failure pointed to the call to verify in the test.

`verify` doesn't fail the test

I noticed that verify doesn't fail the test when the call count doesn't match. I eventually figured out that there's a global closure variable, testFailureReport, that verify calls to report a test failure. But the testFailureReport closure by default doesn't do anything. What's the reason for this? I also haven't found this mentioned in the documentation, that one needs to set testFailureReport to something useful.

Do not generate default values for generic parameters

Due swift language structure using any() for ArgumentMatcher<T> is not possible.

Instead of that framework user should use any<T>(type: T.self) and provide type for stubbing.

We shouldn't generate default value for generic parameters.

Add support for @Mock to extension of a protocol

To enable clean separation of testing and production code it would be nice to be able to place @mock on an extension of a protocol.

Production code:
protocol MyAwesomeProtocol {
func doAmazingThings()
}

Test code:
@mock
extension MyAwesomeProtocol {}

This will add the MyAwesomeProtocolMock in the testing code instead of the production code.

Support diagnostic

The compiler should give the correct error messages why it is impossible to create a mock for the protocol at the moment.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.